在使用 CRTP 的 class 中检测成员函数
Detecting a member function in a class which uses CRTP
我正在尝试根据使用 CRTP 的子 class 中可用的功能自定义基础 classes 的实现。
我想要的基本想法:
// has_inc_function<Child, void> should detect the presence of a member function void Child::inc()
template<class Child, bool = has_inc_function<Child, void>::value>
struct base
{
// ... base implementation stuff
};
template<class Child>
struct base<Child, true>
{
// ... base specialization implementation stuff
};
struct empty : public base<empty>
{};
struct has_inc
{
void inc()
{}
};
struct has_inc_and_crtp : public base<has_inc_and_crtp>
{
void inc()
{}
};
struct has_inc_and_misuse_crtp : public base<has_inc_and_misuse_crtp, true>
{
void inc()
{}
};
struct has_inc_and_misuse_crtp2 : public base<has_inc_and_misuse_crtp, false>
{
void inc()
{}
};
struct no_inc_and_misuse_crtp : public base<no_inc_and_misuse_crtp, true>
{
};
int main()
{
static_assert(has_inc_function<empty, void>::value == false, "");
static_assert(has_inc_function<has_inc, void>::value == true, "");
static_assert(has_inc_function<has_inc_and_crtp, void>::value == true, "");
static_assert(has_inc_function<has_inc_and_misuse_crtp, void>::value == true, "");
static_assert(has_inc_function<has_inc_and_misuse_crtp2, void>::value == true, "");
static_assert(has_inc_function<no_inc_and_misuse_crtp, void>::value == false, "");
}
我为 has_inc_function<Child, void>
尝试了多种不同的实现,但在 has_inc_and_crtp
的情况下,它们似乎都失败了,我不明白为什么。我通过 Compiler Explorer 测试了几个不同的编译器,它们似乎都给出了相同的结果。
我将如何实现 has_inc_function
以便它在所有这些测试用例中都像我期望的那样工作,或者我想要的是不可能的?
我尝试过的实现方式
jrok's solution (Compiler Explorer link):
template <class C, class Ret>
struct has_increment<C, Ret>
{
private:
template <class T>
static constexpr auto check(T*) -> typename std::is_same<
decltype(std::declval<T>().inc()), Ret>::type;
template <typename> static constexpr std::false_type check(...);
typedef decltype(check<C>(nullptr)) type;
public:
static constexpr bool value = type::value;
};
注意:实现与 return 类型不匹配。我还在 Library fundamentals TS v2 中包含了内容的示例实现,以使其在 C++14
中工作
struct nonesuch
{
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};
namespace detail {
template <class Default, class AlwaysVoid,
template<class...> class Op, class... Args>
struct detector {
using value_t = std::false_type;
using type = Default;
};
template <class Default, template<class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
using value_t = std::true_type;
using type = Op<Args...>;
};
} // namespace detail
template <template<class...> class Op, class... Args>
using is_detected = typename detail::detector<nonesuch, void, Op, Args...>::value_t;
template <template<class...> class Op, class... Args>
using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;
template <class Default, template<class...> class Op, class... Args>
using detected_or = detail::detector<Default, void, Op, Args...>;
template<class...> struct disjunction : std::false_type { };
template<class B1> struct disjunction<B1> : B1 { };
template<class B1, class... Bn>
struct disjunction<B1, Bn...>
: std::conditional_t<bool(B1::value), B1, disjunction<Bn...>> { };
template <typename T>
using has_type_t = typename T::inc;
template <typename T>
using has_non_type_t = decltype(&T::inc);
template <typename T, class RetType>
using has_inc_function =
disjunction<is_detected<has_type_t, T>, is_detected<has_non_type_t, T>>;
Valentin Milea's solution (Compiler Explorer Link):
template <class C, class RetType>
class has_inc_function
{
template <class T>
static std::true_type testSignature(RetType (T::*)());
template <class T>
static decltype(testSignature(&T::inc)) test(std::nullptr_t);
template <class T>
static std::false_type test(...);
public:
using type = decltype(test<C>(nullptr));
static const bool value = type::value;
};
Boost TTI(我不知道如何让 Boost 与 Compiler Explorer 一起工作):
#include <boost/tti/has_member_function.hpp>
BOOST_TTI_TRAIT_HAS_MEMBER_FUNCTION(has_inc_function, inc);
你想要的在这种形式下显然是不可能的。必须在 class 完成之前知道 class 的父级,因此在知道 class 是否具有这样的成员函数之前。
你能做什么有点取决于 base 的不同实例化有多么不同。如果它们基本上是具有不同实现细节的相同接口,您可以编写另一个具有相同接口和变体成员的 class(std::variant
很遗憾是 C++17,但您可以使用动态多态性),所有调用都被转发到。然后在实例化的时候就可以决定使用哪个了。
你也可以在这个方向尝试一下:
#include <type_traits>
#include <iostream>
template<class Child>
struct base {
int foo();
};
struct has_inc: base<has_inc> {
void inc();
};
struct has_not_inc: base<has_not_inc> {
};
template<class Child, class = std::void_t<decltype(std::declval<Child>().inc())>>
struct mock {
int foo(base<Child>*) { return 1;}
};
template<class Child>
struct mock<Child> {
int foo(base<Child>*) { return 0;}
};
template<class Child>
int base<Child>::foo() {
return mock<Child,void>().foo(this);
}
int main() {
has_inc h;
has_not_inc n;
std::cout << h.foo() << " " << n.foo() << '\n';
}
这里你只在定义中使用了类型的完整子类,而不是在声明中。就定义而言,完整的子项可用,但在声明期间不可用。
还有其他方法(我认为,一切都不是那么容易),我认为你可以使用什么取决于你的用例。
PS: std::void_t
是 C++17,但它只是 template<class...> using void_t = void;
.
I've tried a variety of different implementations for has_inc_function<Child, void>
, but all of them seem to fail on the case has_inc_and_crtp
, and I can't figure out why.
问题(如果我理解正确的话)是,在 has_inc_and_crpt
的情况下,首先评估 has_inc_function
的值以确定第二个 Child
的默认值模板参数
template<class Child, bool = has_inc_function<Child, void>::value>
struct base
也就是当Child
(也就是has_inc_and_crpt
)还不完整,所以取值iffalse
,在下面使用
static_assert(has_inc_function<has_inc_and_crtp, void>::value == true, "");
保持false
.
How would I implement has_inc_function
so that it works as I would expect in all these test case, or is what I want just not possible?
一个快速而肮脏的解决方案可以是向 has_inc_function
添加一个额外的虚拟默认模板参数。
举例
// ................................VVVVVVV dummy and defaulted
template <typename C, typename RT, int = 0>
struct has_inc_function
然后在base
中使用它来解释一个特殊的(不同于默认的)参数
// ........................................................V different from the default
template<class Child, bool = has_inc_function<Child, void, 1>::value>
struct base
因此,当您在静态断言中使用 has_inc_functin
时,
static_assert(has_inc_function<has_inc_and_crtp, void>::value == true, "");
class是不同的,是在那一刻评估的,而has_inc_and_crpt
是用inc()
方法检测到的。
但这只能解决测试用例 (static_assert()
) 级别的问题。
仍然存在问题(一个我不知道如何解决的问题),声明 base
,默认值仍然为 false。所以(我想)has_inc_and_crpt
仍然 select 错误的 base
基础。
以下是完整的编译示例,遵循jrok的解决方案。
#include <type_traits>
template <typename C, typename RT, int = 0>
struct has_inc_function
{
private:
template <typename T>
static constexpr auto check(T *) ->
typename std::is_same<decltype(std::declval<T>().inc()), RT>::type;
template <typename>
static constexpr std::false_type check(...);
using type = decltype(check<C>(nullptr));
public:
/// @brief True if there is an inc member function
static constexpr bool value = type::value;
};
template <typename Child, bool = has_inc_function<Child, void, 1>::value>
struct base
{ };
template <typename Child>
struct base<Child, true>
{ };
struct empty : public base<empty>
{ };
struct has_inc
{ void inc() {} };
struct has_inc_and_crtp : public base<has_inc_and_crtp>
{ void inc() {} };
struct has_inc_and_misuse_crtp : public base<has_inc_and_misuse_crtp, true>
{ void inc() {} };
struct has_inc_and_misuse_crtp2 : public base<has_inc_and_misuse_crtp, false>
{ void inc() {} };
struct no_inc_and_misuse_crtp : public base<no_inc_and_misuse_crtp, true>
{ };
template <typename C, typename RT>
constexpr auto hif_v = has_inc_function<C, RT>::value;
int main ()
{
static_assert(hif_v<empty, void> == false, "");
static_assert(hif_v<has_inc, void> == true, "");
static_assert(hif_v<has_inc_and_crtp, void> == true, "");
static_assert(hif_v<has_inc_and_misuse_crtp, void> == true, "");
static_assert(hif_v<has_inc_and_misuse_crtp2, void> == true, "");
static_assert(hif_v<no_inc_and_misuse_crtp, void> == false, "");
}
我正在尝试根据使用 CRTP 的子 class 中可用的功能自定义基础 classes 的实现。
我想要的基本想法:
// has_inc_function<Child, void> should detect the presence of a member function void Child::inc()
template<class Child, bool = has_inc_function<Child, void>::value>
struct base
{
// ... base implementation stuff
};
template<class Child>
struct base<Child, true>
{
// ... base specialization implementation stuff
};
struct empty : public base<empty>
{};
struct has_inc
{
void inc()
{}
};
struct has_inc_and_crtp : public base<has_inc_and_crtp>
{
void inc()
{}
};
struct has_inc_and_misuse_crtp : public base<has_inc_and_misuse_crtp, true>
{
void inc()
{}
};
struct has_inc_and_misuse_crtp2 : public base<has_inc_and_misuse_crtp, false>
{
void inc()
{}
};
struct no_inc_and_misuse_crtp : public base<no_inc_and_misuse_crtp, true>
{
};
int main()
{
static_assert(has_inc_function<empty, void>::value == false, "");
static_assert(has_inc_function<has_inc, void>::value == true, "");
static_assert(has_inc_function<has_inc_and_crtp, void>::value == true, "");
static_assert(has_inc_function<has_inc_and_misuse_crtp, void>::value == true, "");
static_assert(has_inc_function<has_inc_and_misuse_crtp2, void>::value == true, "");
static_assert(has_inc_function<no_inc_and_misuse_crtp, void>::value == false, "");
}
我为 has_inc_function<Child, void>
尝试了多种不同的实现,但在 has_inc_and_crtp
的情况下,它们似乎都失败了,我不明白为什么。我通过 Compiler Explorer 测试了几个不同的编译器,它们似乎都给出了相同的结果。
我将如何实现 has_inc_function
以便它在所有这些测试用例中都像我期望的那样工作,或者我想要的是不可能的?
我尝试过的实现方式
jrok's solution (Compiler Explorer link):
template <class C, class Ret>
struct has_increment<C, Ret>
{
private:
template <class T>
static constexpr auto check(T*) -> typename std::is_same<
decltype(std::declval<T>().inc()), Ret>::type;
template <typename> static constexpr std::false_type check(...);
typedef decltype(check<C>(nullptr)) type;
public:
static constexpr bool value = type::value;
};
注意:实现与 return 类型不匹配。我还在 Library fundamentals TS v2 中包含了内容的示例实现,以使其在 C++14
中工作struct nonesuch
{
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};
namespace detail {
template <class Default, class AlwaysVoid,
template<class...> class Op, class... Args>
struct detector {
using value_t = std::false_type;
using type = Default;
};
template <class Default, template<class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
using value_t = std::true_type;
using type = Op<Args...>;
};
} // namespace detail
template <template<class...> class Op, class... Args>
using is_detected = typename detail::detector<nonesuch, void, Op, Args...>::value_t;
template <template<class...> class Op, class... Args>
using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;
template <class Default, template<class...> class Op, class... Args>
using detected_or = detail::detector<Default, void, Op, Args...>;
template<class...> struct disjunction : std::false_type { };
template<class B1> struct disjunction<B1> : B1 { };
template<class B1, class... Bn>
struct disjunction<B1, Bn...>
: std::conditional_t<bool(B1::value), B1, disjunction<Bn...>> { };
template <typename T>
using has_type_t = typename T::inc;
template <typename T>
using has_non_type_t = decltype(&T::inc);
template <typename T, class RetType>
using has_inc_function =
disjunction<is_detected<has_type_t, T>, is_detected<has_non_type_t, T>>;
Valentin Milea's solution (Compiler Explorer Link):
template <class C, class RetType>
class has_inc_function
{
template <class T>
static std::true_type testSignature(RetType (T::*)());
template <class T>
static decltype(testSignature(&T::inc)) test(std::nullptr_t);
template <class T>
static std::false_type test(...);
public:
using type = decltype(test<C>(nullptr));
static const bool value = type::value;
};
Boost TTI(我不知道如何让 Boost 与 Compiler Explorer 一起工作):
#include <boost/tti/has_member_function.hpp>
BOOST_TTI_TRAIT_HAS_MEMBER_FUNCTION(has_inc_function, inc);
你想要的在这种形式下显然是不可能的。必须在 class 完成之前知道 class 的父级,因此在知道 class 是否具有这样的成员函数之前。
你能做什么有点取决于 base 的不同实例化有多么不同。如果它们基本上是具有不同实现细节的相同接口,您可以编写另一个具有相同接口和变体成员的 class(std::variant
很遗憾是 C++17,但您可以使用动态多态性),所有调用都被转发到。然后在实例化的时候就可以决定使用哪个了。
你也可以在这个方向尝试一下:
#include <type_traits>
#include <iostream>
template<class Child>
struct base {
int foo();
};
struct has_inc: base<has_inc> {
void inc();
};
struct has_not_inc: base<has_not_inc> {
};
template<class Child, class = std::void_t<decltype(std::declval<Child>().inc())>>
struct mock {
int foo(base<Child>*) { return 1;}
};
template<class Child>
struct mock<Child> {
int foo(base<Child>*) { return 0;}
};
template<class Child>
int base<Child>::foo() {
return mock<Child,void>().foo(this);
}
int main() {
has_inc h;
has_not_inc n;
std::cout << h.foo() << " " << n.foo() << '\n';
}
这里你只在定义中使用了类型的完整子类,而不是在声明中。就定义而言,完整的子项可用,但在声明期间不可用。
还有其他方法(我认为,一切都不是那么容易),我认为你可以使用什么取决于你的用例。
PS: std::void_t
是 C++17,但它只是 template<class...> using void_t = void;
.
I've tried a variety of different implementations for
has_inc_function<Child, void>
, but all of them seem to fail on the casehas_inc_and_crtp
, and I can't figure out why.
问题(如果我理解正确的话)是,在 has_inc_and_crpt
的情况下,首先评估 has_inc_function
的值以确定第二个 Child
的默认值模板参数
template<class Child, bool = has_inc_function<Child, void>::value>
struct base
也就是当Child
(也就是has_inc_and_crpt
)还不完整,所以取值iffalse
,在下面使用
static_assert(has_inc_function<has_inc_and_crtp, void>::value == true, "");
保持false
.
How would I implement
has_inc_function
so that it works as I would expect in all these test case, or is what I want just not possible?
一个快速而肮脏的解决方案可以是向 has_inc_function
添加一个额外的虚拟默认模板参数。
举例
// ................................VVVVVVV dummy and defaulted
template <typename C, typename RT, int = 0>
struct has_inc_function
然后在base
中使用它来解释一个特殊的(不同于默认的)参数
// ........................................................V different from the default
template<class Child, bool = has_inc_function<Child, void, 1>::value>
struct base
因此,当您在静态断言中使用 has_inc_functin
时,
static_assert(has_inc_function<has_inc_and_crtp, void>::value == true, "");
class是不同的,是在那一刻评估的,而has_inc_and_crpt
是用inc()
方法检测到的。
但这只能解决测试用例 (static_assert()
) 级别的问题。
仍然存在问题(一个我不知道如何解决的问题),声明 base
,默认值仍然为 false。所以(我想)has_inc_and_crpt
仍然 select 错误的 base
基础。
以下是完整的编译示例,遵循jrok的解决方案。
#include <type_traits>
template <typename C, typename RT, int = 0>
struct has_inc_function
{
private:
template <typename T>
static constexpr auto check(T *) ->
typename std::is_same<decltype(std::declval<T>().inc()), RT>::type;
template <typename>
static constexpr std::false_type check(...);
using type = decltype(check<C>(nullptr));
public:
/// @brief True if there is an inc member function
static constexpr bool value = type::value;
};
template <typename Child, bool = has_inc_function<Child, void, 1>::value>
struct base
{ };
template <typename Child>
struct base<Child, true>
{ };
struct empty : public base<empty>
{ };
struct has_inc
{ void inc() {} };
struct has_inc_and_crtp : public base<has_inc_and_crtp>
{ void inc() {} };
struct has_inc_and_misuse_crtp : public base<has_inc_and_misuse_crtp, true>
{ void inc() {} };
struct has_inc_and_misuse_crtp2 : public base<has_inc_and_misuse_crtp, false>
{ void inc() {} };
struct no_inc_and_misuse_crtp : public base<no_inc_and_misuse_crtp, true>
{ };
template <typename C, typename RT>
constexpr auto hif_v = has_inc_function<C, RT>::value;
int main ()
{
static_assert(hif_v<empty, void> == false, "");
static_assert(hif_v<has_inc, void> == true, "");
static_assert(hif_v<has_inc_and_crtp, void> == true, "");
static_assert(hif_v<has_inc_and_misuse_crtp, void> == true, "");
static_assert(hif_v<has_inc_and_misuse_crtp2, void> == true, "");
static_assert(hif_v<no_inc_and_misuse_crtp, void> == false, "");
}