检查类是否具有给定签名的成员函数

我要求使用一个模板技巧来检测一个类是否具有给定签名的特定成员函数。

这个问题与这里引用的问题相似 Http://www.gotw.ca/gotw/071.htm 但是不一样: 在 Sutter 的书中,他回答了这样一个问题: 类 C 必须提供一个带有特定签名的成员函数,否则程序就不能编译。在我的问题中,如果一个类有这个函数,我需要做一些事情,否则就做“别的事情”。

序列化也面临着类似的问题,但我不喜欢他们采用的解决方案: 一个模板函数默认调用一个带有特定签名的自由函数(你必须定义) ,除非你定义了一个带有特定签名的特定成员函数(在他们的情况下“序列化”带有给定类型的2个参数) ,否则就会发生编译错误。即同时实现侵入性和非侵入性序列化。

我不喜欢这个解决方案有两个原因:

  1. 为了不打扰您,您必须覆盖存在于升级: : 序列化名称空间中的全局“序列化”函数,这样您就可以在您的客户端代码中打开名称空间升级和名称空间序列化!
  2. 解决该问题的堆栈 混乱是10到12个函数调用。

我需要为没有这个成员函数的类定义一个自定义行为,并且我的实体位于不同的名称空间中(我不想在另一个名称空间中覆盖在一个名称空间中定义的全局函数)

你能给我一个解决这个难题的提示吗?

114036 次浏览

为了非侵入性,您还可以将 serialize放在正在序列化的类的名称空间中,或者放在归档类的名称空间中,这要感谢 凯尼格查询。有关详细信息,请参阅 用于自由函数重写的命名空间。:-)

打开任何给定的命名空间来实现一个免费的函数是完全错误的。(例如,您不应该打开名称空间 std来为自己的类型实现 swap,而应该使用 Koenig 查找。)

好吧,再试一次,如果你也不喜欢这个也没关系,我在寻找更多的点子。

Herb Sutter 的文章谈到了性格特征。所以你可以有一个 trait 类,它的默认实例化具有回退行为,对于存在成员函数的每个类,trait 类都是专门用来调用成员函数的。我相信 Herb 的文章提到了一种技术来做到这一点,这样就不会涉及到大量的复制和粘贴。

但是,正如我所说的,您可能不希望实现该成员的“标记”类涉及额外的工作。在这种情况下,我正在寻找第三种解决方案... 。

我不确定我是否正确理解您的意思,但是您可以利用 SFINAE 在编译时检测函数的存在。来自我的代码的示例(测试 class 是否有成员函数 size _ t used _ memory () const)。

template<typename T>
struct HasUsedMemoryMethod
{
template<typename U, size_t (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
template<typename U> static int Test(...);
static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};


template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
// We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
ReportMemUsage(m,
std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}

如果您知道所期望的成员函数的名称,那么这就足够了。(在这种情况下,如果没有成员函数,那么 bla 函数就无法实例化(编写一个能够正常工作的函数是很困难的,因为缺乏函数的部分专门化。您可能需要使用类模板)此外,还可以对作为成员的函数的类型进行模板化,使结构(类似于 able _ if)。

template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
A a;
B b;
bla(b);
bla(a);
}

编译时成员函数问题的公认答案 尽管自省理所当然地受到欢迎,但它也有一个可以观察到的障碍 在以下节目中:

#include <type_traits>
#include <iostream>
#include <memory>


/*  Here we apply the accepted answer's technique to probe for the
the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
template<typename U, E (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::operator*>*);
template<typename U> static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};


using namespace std;


/* Here we test the `std::` smart pointer templates, including the
deprecated `auto_ptr<T>`, to determine in each case whether
T = (the template instantiated for `int`) provides
`int & T::operator*() const` - which all of them in fact do.
*/
int main(void)
{
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
return 0;
}

使用 GCC 4.6.3构建,程序输出 110-通知我们 T = std::shared_ptr<int>没有提供 int & T::operator*() const

如果你还没有意识到这个问题,那么看看 标题 <memory>中的 std::shared_ptr<T>会显示出来 实现时,std::shared_ptr<T>是从基类派生的 所以模板实例化 构成“查找”操作符的 SFINAE<U, &U::operator*> U = std::shared_ptr<T>不会发生,因为 std::shared_ptr<T>没有 operator*()在自己的权利和模板实例化不 “继承遗产”。

这个障碍并不影响著名的 SFINAE 方法,它使用了“ sizeof ()技巧”, 仅用于检测 T是否具有某种成员函数 mf(参见。 这个答案 和评论) 确定 T::mf的存在通常(通常?)是不够好的: 你可以 也需要确定它有一个理想的签名。这就是 插图技术分数。所需签名的指定变体 必须满足的模板类型的参数中 为 SFINAE 探测器的 &T::mf成功。但这个模板实例化 技术给出了错误的答案时,T::mf是继承。

一个安全的用于 T::mf编译时自省的 SFINAE 技术必须避免 在模板参数中使用 &T::mf来实例化 SFINAE 所依赖的类型 函数模板分辨率取决于。相反,SFINAE 模板函数 解决方案只能依赖于所使用的完全相关的类型声明 作为重载 SFINAE 探测函数的参数类型。

为了回答遵守这个约束的问题,我将 对于 E T::operator*() const的编译时检测,如下所示 任意 TE。同样的模式将应用 比照适用 探测任何其他成员方法签名。

#include <type_traits>


/*! The template `has_const_reference_op<T,E>` exports a
boolean constant `value that is true iff `T` provides
`E T::operator*() const`
*/
template< typename T, typename E>
struct has_const_reference_op
{
/* SFINAE operator-has-correct-sig :) */
template<typename A>
static std::true_type test(E (A::*)() const) {
return std::true_type();
}


/* SFINAE operator-exists :) */
template <typename A>
static decltype(test(&A::operator*))
test(decltype(&A::operator*),void *) {
/* Operator exists. What about sig? */
typedef decltype(test(&A::operator*)) return_type;
return return_type();
}


/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}


/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;


static const bool value = type::value; /* Which is it? */
};

在这个解决方案中,重载的 SFINAE 探测函数 test()被“调用” (当然,它实际上根本没有被调用; 它只是 由编译器解析的假设调用的返回类型。)

我们需要探究至少一点,最多两点信息:

  • T::operator*()存在吗? 如果不存在,我们就完了。
  • 假设 T::operator*()存在,那么它的签名就是 E T::operator*() const

我们通过计算单个调用的返回类型来得到答案 到 test(0,0)。这是通过:

    typedef decltype(test<T>(0,0)) type;

此调用可以解析为 /* SFINAE operator-exists :) */重载 或者它可能解析为 /* SFINAE game over :( */过载。 无法解析 /* SFINAE operator-has-correct-sig :) */过载, 因为它只需要一个参数,我们传递了两个参数。

为什么我们要通过两个? 仅仅是为了迫使决议排除 第二个参数没有其他意义。

这个对 test(0,0)的调用将解析为 /* SFINAE operator-exists :) */ 如果第一个参数0满足该重载的第一个参数类型, 它是 decltype(&A::operator*),带有 A = T.0将满足该类型 以防 T::operator*存在。

让我们假设编译器对此说“是”,那么它就是 它需要确定返回类型为 函数调用,在这种情况下是 decltype(test(&A::operator*))- 对 test()的另一个调用的返回类型。

这一次,我们只传递一个参数 &A::operator* 知道存在,否则我们不会在这里。一个电话 test(&A::operator*)可能 解析为 /* SFINAE operator-has-correct-sig :) */或再解析为 可能解析为 /* SFINAE game over :( */。调用将匹配 以防 &A::operator*满足要求 该重载的单个参数类型,即 E (A::*)() const, 与 A = T

如果 T::operator*具有所需的签名,编译器将在这里说 Yes, 然后再次计算重载的返回类型 现在的“递归”是 std::true_type

如果编译器没有为 调用 test(0,0)或不选择 /* SFINAE operator-has-correct-sig :) */ 调用 test(&A::operator*),然后在任何情况下,它与 最终的返回类型是 std::false_type

下面是一个测试程序,它显示了生成预期结果的模板 答案在不同的情况下抽样(海湾合作委员会4.6.3再次)。

// To test
struct empty{};


// To test
struct int_ref
{
int & operator*() const {
return *_pint;
}
int & foo() const {
return *_pint;
}
int * _pint;
};


// To test
struct sub_int_ref : int_ref{};


// To test
template<typename E>
struct ee_ref
{
E & operator*() {
return *_pe;
}
E & foo() const {
return *_pe;
}
E * _pe;
};


// To test
struct sub_ee_ref : ee_ref<char>{};


using namespace std;


#include <iostream>
#include <memory>
#include <vector>


int main(void)
{
cout << "Expect Yes" << endl;
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value;
cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
cout << has_const_reference_op<std::vector<int>::const_iterator,
int const &>::value;
cout << has_const_reference_op<int_ref,int &>::value;
cout << has_const_reference_op<sub_int_ref,int &>::value  << endl;
cout << "Expect No" << endl;
cout << has_const_reference_op<int *,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,char &>::value;
cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
cout << has_const_reference_op<unique_ptr<int>,int>::value;
cout << has_const_reference_op<unique_ptr<long>,int &>::value;
cout << has_const_reference_op<int,int>::value;
cout << has_const_reference_op<std::vector<int>,int &>::value;
cout << has_const_reference_op<ee_ref<int>,int &>::value;
cout << has_const_reference_op<sub_ee_ref,int &>::value;
cout << has_const_reference_op<empty,int &>::value  << endl;
return 0;
}

这个想法是否有新的缺陷? 它能否变得更加通用而不再重复 与它所避免的障碍发生冲突?

你可以使用 < a href = “ http://en.cpferences ence.com/w/cpp/types/is _ member _ function _ point”rel = “ noReferrer”> std: : is _ member _ function _ point

class A {
public:
void foo() {};
}


bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;

我自己也遇到了同样的问题,发现这里提出的解决方案非常有趣... ... 但是需要一个解决方案:

  1. 同时检测继承的函数;
  2. 与非 C + + 11就绪编译器兼容(因此没有 dectype)

发现另一个 线提出了类似的东西,基于一个 推进讨论。 下面是按照 Has _ * 类的模型将提出的解决方案推广为 trait 类的两个宏声明。

#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>


/// Has constant function
/** \param func_ret_type Function return type
\param func_name Function name
\param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
__DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)


/// Has non-const function
/** \param func_ret_type Function return type
\param func_name Function name
\param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
__DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)


// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...)  \
template                                                                  \
<   typename Type,                                                        \
bool is_class = boost::is_class<Type>::value                          \
>                                                                         \
class has_func_ ## func_name;                                             \
template<typename Type>                                                   \
class has_func_ ## func_name<Type,false>                                  \
{public:                                                                  \
BOOST_STATIC_CONSTANT( bool, value = false );                         \
typedef boost::false_type type;                                       \
};                                                                        \
template<typename Type>                                                   \
class has_func_ ## func_name<Type,true>                                   \
{   struct yes { char _foo; };                                            \
struct no { yes _foo[2]; };                                           \
struct Fallback                                                       \
{   func_ret_type func_name( __VA_ARGS__ )                            \
UTILITY_OPTIONAL(func_const,const) {}                         \
};                                                                    \
struct Derived : public Type, public Fallback {};                     \
template <typename T, T t>  class Helper{};                           \
template <typename U>                                                 \
static no deduce(U*, Helper                                           \
<   func_ret_type (Fallback::*)( __VA_ARGS__ )                    \
UTILITY_OPTIONAL(func_const,const),                       \
&U::func_name                                                 \
>* = 0                                                            \
);                                                                    \
static yes deduce(...);                                               \
public:                                                                   \
BOOST_STATIC_CONSTANT(                                                \
bool,                                                             \
value = sizeof(yes)                                               \
== sizeof( deduce( static_cast<Derived*>(0) ) )               \
);                                                                    \
typedef ::boost::integral_constant<bool,value> type;                  \
BOOST_STATIC_CONSTANT(bool, is_const = func_const);                   \
typedef func_ret_type return_type;                                    \
typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;                \
}


// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__

这些宏可以扩展成以下原型的 trait 类:

template<class T>
class has_func_[func_name]
{
public:
/// Function definition result value
/** Tells if the tested function is defined for type T or not.
*/
static const bool value = true | false;


/// Function definition result type
/** Type representing the value attribute usable in
http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
*/
typedef boost::integral_constant<bool,value> type;


/// Tested function constness indicator
/** Indicates if the tested function is const or not.
This value is not deduced, it is forced depending
on the user call to one of the traits generators.
*/
static const bool is_const = true | false;


/// Tested function return type
/** Indicates the return type of the tested function.
This value is not deduced, it is forced depending
on the user's arguments to the traits generators.
*/
typedef func_ret_type return_type;


/// Tested function arguments types
/** Indicates the arguments types of the tested function.
This value is not deduced, it is forced depending
on the user's arguments to the traits generators.
*/
typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};

那么,通常的用法是什么呢?

// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
// Next line will declare the traits class
// to detect the member function void foo(int,int) const
DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}


// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>


// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   _this_.foo(a,b);
}


// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   default_foo(_this_,a,b);
}


// Let us declare test types
struct empty
{
};
struct direct_foo
{
void foo(int,int);
};
struct direct_const_foo
{
void foo(int,int) const;
};
struct inherited_const_foo :
public direct_const_foo
{
};


// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
int a;
foo_bar(a); // calls default_foo


empty b;
foo_bar(b); // calls default_foo


direct_foo c;
foo_bar(c); // calls default_foo (member function is not const)


direct_const_foo d;
foo_bar(d); // calls d.foo (member function is const)


inherited_const_foo e;
foo_bar(e); // calls e.foo (inherited member function)
}

下面是一个可能依赖于 C + + 11特性的实现。即使函数是继承的,它也能正确地检测出来(不像 Mike Kinghan 在 他的回答中所观察到的公认答案中的解)。

这个代码片段测试的函数名为 serialize:

#include <type_traits>


// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.


template<typename, typename T>
struct has_serialize {
static_assert(
std::integral_constant<T, false>::value,
"Second template parameter needs to be of function type.");
};


// specialization that does the checking


template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
template<typename T>
static constexpr auto check(T*)
-> typename
std::is_same<
decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
Ret    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>::type;  // attempt to call it and see if the return type is correct


template<typename>
static constexpr std::false_type check(...);


typedef decltype(check<C>(0)) type;


public:
static constexpr bool value = type::value;
};

用法:

struct X {
int serialize(const std::string&) { return 42; }
};


struct Y : X {};


std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1

下面是一些用法片段: 这一切的勇气都在更深的地方 *

检查给定类中的成员 x。可以是 var、 func、 class、 union 或 enum:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

检查成员功能 void x():

//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;

检查成员变量 x:

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

检查成员类 x:

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

检查成员工会 x:

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

检查成员枚举 x:

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

检查任何成员函数 x,无论签名如何:

CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

或者

CREATE_MEMBER_CHECKS(x);  //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

详情及核心资料:

/*
- Multiple inheritance forces ambiguity of member names.
- SFINAE is used to make aliases to member names.
- Expression SFINAE is used in just one generic has_member that can accept
any alias we pass it.
*/


//Variadic to force ambiguity of class members.  C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};


//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};


template<typename A, typename = void>
struct got_type : std::false_type {};


template<typename A>
struct got_type<A> : std::true_type {
typedef A type;
};


template<typename T, T>
struct sig_check : std::true_type {};


template<typename Alias, typename AmbiguitySeed>
struct has_member {
template<typename C> static char ((&f(decltype(&C::value))))[1];
template<typename C> static char ((&f(...)))[2];


//Make sure the member name is consistently spelled the same.
static_assert(
(sizeof(f<AmbiguitySeed>(0)) == 1)
, "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
);


static bool const value = sizeof(f<Alias>(0)) == 2;
};

宏(暗黑破坏神!) :

创建 _ 成员 _ 检查:

//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member)                                         \
\
template<typename T, typename = std::true_type>                             \
struct Alias_##member;                                                      \
\
template<typename T>                                                        \
struct Alias_##member <                                                     \
T, std::integral_constant<bool, got_type<decltype(&T::member)>::value>  \
> { static const decltype(&T::member) value; };                             \
\
struct AmbiguitySeed_##member { char member; };                             \
\
template<typename T>                                                        \
struct has_member_##member {                                                \
static const bool value                                                 \
= has_member<                                                       \
Alias_##member<ambiguate<T, AmbiguitySeed_##member>>            \
, Alias_##member<AmbiguitySeed_##member>                        \
>::value                                                            \
;                                                                       \
}

创建 _ MEMBER _ VAR _ CHECK:

//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name)                                   \
\
template<typename T, typename = std::true_type>                             \
struct has_member_var_##var_name : std::false_type {};                      \
\
template<typename T>                                                        \
struct has_member_var_##var_name<                                           \
T                                                                       \
, std::integral_constant<                                               \
bool                                                                \
, !std::is_member_function_pointer<decltype(&T::var_name)>::value   \
>                                                                       \
> : std::true_type {}

创建 _ MEMBER _ FUNC _ SIG _ CHECK:

//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix)    \
\
template<typename T, typename = std::true_type>                             \
struct has_member_func_##templ_postfix : std::false_type {};                \
\
template<typename T>                                                        \
struct has_member_func_##templ_postfix<                                     \
T, std::integral_constant<                                              \
bool                                                                \
, sig_check<func_sig, &T::func_name>::value                         \
>                                                                       \
> : std::true_type {}

创建 _ MEMBER _ Class _ CHECK:

//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name)               \
\
template<typename T, typename = std::true_type>             \
struct has_member_class_##class_name : std::false_type {};  \
\
template<typename T>                                        \
struct has_member_class_##class_name<                       \
T                                                       \
, std::integral_constant<                               \
bool                                                \
, std::is_class<                                    \
typename got_type<typename T::class_name>::type \
>::value                                            \
>                                                       \
> : std::true_type {}

创建 _ MEMBER _ UNION _ CHECK:

//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name)               \
\
template<typename T, typename = std::true_type>             \
struct has_member_union_##union_name : std::false_type {};  \
\
template<typename T>                                        \
struct has_member_union_##union_name<                       \
T                                                       \
, std::integral_constant<                               \
bool                                                \
, std::is_union<                                    \
typename got_type<typename T::union_name>::type \
>::value                                            \
>                                                       \
> : std::true_type {}

创建 _ MEMBER _ ENUM _ CHECK:

//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name)                 \
\
template<typename T, typename = std::true_type>             \
struct has_member_enum_##enum_name : std::false_type {};    \
\
template<typename T>                                        \
struct has_member_enum_##enum_name<                         \
T                                                       \
, std::integral_constant<                               \
bool                                                \
, std::is_enum<                                     \
typename got_type<typename T::enum_name>::type  \
>::value                                            \
>                                                       \
> : std::true_type {}

创建 _ MEMBER _ FUNC _ CHECK:

//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func)          \
template<typename T>                            \
struct has_member_func_##func {                 \
static const bool value                     \
= has_member_##func<T>::value           \
&& !has_member_var_##func<T>::value     \
&& !has_member_class_##func<T>::value   \
&& !has_member_union_##func<T>::value   \
&& !has_member_enum_##func<T>::value    \
;                                           \
}

创建 _ 成员 _ 检查:

//Create all the checks for one member.  Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member)    \
CREATE_MEMBER_CHECK(member);            \
CREATE_MEMBER_VAR_CHECK(member);        \
CREATE_MEMBER_CLASS_CHECK(member);      \
CREATE_MEMBER_UNION_CHECK(member);      \
CREATE_MEMBER_ENUM_CHECK(member);       \
CREATE_MEMBER_FUNC_CHECK(member)

以下是对麦克 · 金汉的回答的一个简单的看法。这将检测继承的方法。它还将检查 一模一样签名(不同于 jrok 允许参数转换的方法)。

template <class C>
class HasGreetMethod
{
template <class T>
static std::true_type testSignature(void (T::*)(const char*) const);


template <class T>
static decltype(testSignature(&T::greet)) 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;
};


struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");

可运行 例子

为了实现这一点,我们需要使用:

  1. 函数模板重载 ,根据方法是否可用,使用不同的返回类型
  2. 为了与 type_traits头中的元条件保持一致,我们希望从重载中返回一个 ABC1或 false_type
  3. 声明 true_type重载期望 intfalse_type重载期望利用变量参数: “重载解析中省略号转换的最低优先级”
  4. 在为 true_type函数定义模板规范时,我们将使用 declvaldecltype,它们允许我们检测函数,而不受方法之间返回类型差异或重载的影响

你可以看到这个 给你的一个实例。 但是我也会在下面解释它:

我想检查是否存在一个名为 test的函数,它接受来自 int的可转换类型,然后我需要声明这两个函数:

template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
  • decltype(hasTest<a>(0))::valuetrue(注意不需要创建特殊的功能来处理 void a::test()过载,void a::test(int)是可以接受的)
  • decltype(hasTest<b>(0))::valuetrue(因为 int可转换为 double,所以 int b::test(double)是可接受的,与返回类型无关)
  • decltype(hasTest<c>(0))::valuefalse(c没有名为 test的方法,该方法接受从 int转换的类型,因此不被接受)

这种解决方案有两个缺点:

  1. 需要一对函数的每个方法声明
  2. 创建名称空间污染,特别是如果我们想要测试类似的名称,例如,我们会命名一个函数,想要测试一个 test()方法?

因此,在详细名称空间中声明这些函数非常重要,或者理想情况下,如果它们仅与类一起使用,则应该由该类私下声明。为此,我编写了一个宏来帮助您抽象这些信息:

#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
template <typename T> static false_type __ ## DEFINE(...); \
template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));

你可以这样使用:

namespace details {
FOO(test(declval<int>()), test_int)
FOO(test(), test_void)
}

随后调用 details::test_int<a>::valuedetails::test_void<a>::value将生成用于内联代码或元编程目的的 truefalse

如果没有 C + + 11支持(decltype) ,这可能会奏效:

SSCCE

#include <iostream>
using namespace std;


struct A { void foo(void); };
struct Aa: public A { };
struct B { };


struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };


template<typename T>
struct FooFinder {
typedef char true_type[1];
typedef char false_type[2];


template<int>
struct TypeSink;


template<class U>
static true_type &match(U);


template<class U>
static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);


template<class U>
static false_type &test(...);


enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};


int main() {
cout << FooFinder<A>::value << endl;
cout << FooFinder<Aa>::value << endl;
cout << FooFinder<B>::value << endl;


cout << FooFinder<retA>::value << endl;
cout << FooFinder<argA>::value << endl;
cout << FooFinder<constA>::value << endl;
cout << FooFinder<varA>::value << endl;
}

希望如何运作

AAaB是有问题的类,Aa是继承我们正在寻找的成员的特殊类。

FooFinder课程中,对应的 c + + 11班级的替身计画是 true_typefalse_type。同样为了理解模板元编程,他们揭示了 SFINAE-sizeof-trick 的基础。

TypeSink是一个模板结构,稍后用于将 sizeof运算符的整数结果接入到模板实例化中以形成类型。

match函数是另一种 SFINAE 类型的模板,它没有通用的对应物。因此,只有当其参数的类型与其专门化的类型匹配时,才能对其进行实例化。

test函数和枚举声明最终形成了中心 SFINAE 模式。还有一种使用省略号的泛型函数,它返回 false_type,另一种使用更具体的参数作为优先级。

为了能够用 T的模板参数实例化 test函数,必须实例化 match函数,因为实例化 TypeSink参数需要返回类型。需要注意的是,包装在函数参数中的 &U::foo是从模板参数专门化中引用的 没有,因此继承的成员查找仍然会发生。

如果你正在使用 facebook 的愚蠢之举,有一个开箱即用的宏来帮助你:

#include <folly/Traits.h>
namespace {
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace


void some_func() {
cout << "Does class Foo have a member int test() const? "
<< boolalpha << has_test_traits<Foo, int() const>::value;
}

尽管实现细节与前一个答案相同,但使用库更简单。

我有一个类似的需要,并得到了这样。这里提出了许多有趣/强大的解决方案,尽管对于特定的需求来说有点长: 检测一个类是否具有具有精确签名的成员函数。因此,我做了一些阅读/测试,并提出了我的版本,可能会感兴趣。它检测到:

  • 静态成员函数静态成员函数
  • 非静态成员函数
  • 非静态成员函数常量

上面有精确的签名。因为我不需要捕获 任何签名(这将需要一个更复杂的解决方案) ,所以这个适合我。它基本上使用的是 启用 _ if _ t

struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };


// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T>
> : std::true_type {};


template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
> : std::true_type {};


template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <T,
std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
> : std::true_type {};


int main ()
{
constexpr bool has_sum_val = has_static_sum<Foo>::value;
constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;


constexpr bool has_calc_val = has_calc<Bar>::value;
constexpr bool not_has_calc_val = !has_calc<Foo>::value;


constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;


std::cout<< "           has_sum_val " << has_sum_val            << std::endl
<< "       not_has_sum_val " << not_has_sum_val        << std::endl
<< "          has_calc_val " << has_calc_val           << std::endl
<< "      not_has_calc_val " << not_has_calc_val       << std::endl
<< "    has_calc_const_val " << has_calc_const_val     << std::endl
<< "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}

产出:

           has_sum_val 1
not_has_sum_val 1
has_calc_val 1
not_has_calc_val 1
has_calc_const_val 1
not_has_calc_const_val 1

你似乎想要探测器这个成语。上面的答案是 C + + 11或 C + + 14中的变体。

std::experimental库有一些基本上可以做到这一点的特性:

#include <experimental/type_traits>


// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>().serialize(std::declval<int>()));


// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;


如果你不能使用 std: : 实验版,可以这样做一个基本的版本:

template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};


// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>().serialize(std::declval<int>()));


// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;

由于 has _ seralize _ t 实际上是 std: : true _ type 或 std: : false _ type,因此可以通过 SFINAE 中的任何常见习惯用法使用它:

template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}

或者通过使用具有重载解决方案的分派:

template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
// call serialize here.
}


template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
// do something else here.
}


template<class T>
std::string Serialize(const T& t) {
return SerializeImpl(has_serialize_t<T>{}, t);
}


基于 Jrok回答,我避免使用嵌套的模板类和/或函数。

#include <type_traits>


#define CHECK_NESTED_FUNC(fName) \
template <typename, typename, typename = std::void_t<>> \
struct _has_##fName \
: public std::false_type {}; \
\
template <typename Class, typename Ret, typename... Args> \
struct _has_##fName<Class, Ret(Args...), \
std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
: public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
{}; \
\
template <typename Class, typename Signature> \
using has_##fName = _has_##fName<Class, Signature>;


#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value

我们可以使用以下宏:

class Foo
{
public:
void Bar(int, const char *) {}
};


CHECK_NESTED_FUNC(Bar);  // generate required metafunctions


int main()
{
using namespace std;
cout << boolalpha
<< HAS_NESTED_FUNC(Foo, Bar, void(int, const char *))  // prints true
<< endl;
return 0;
}

欢迎提出建议。

使用 c + + 20,这将变得简单得多。假设我们想测试一个类 T是否有一个成员函数 void T::resize(typename T::size_type)。例如,std::vector<U>有这样一个成员函数。然后,

template<typename T>
concept has_resize_member_func = requires {
typename T::size_type;
{ std::declval<T>().resize(std::declval<typename T::size_type>()) } -> std::same_as<void>;
};

用法是

static_assert(has_resize_member_func<std::string>, "");
static_assert(has_resize_member_func<int> == false, "");