模板检查类成员函数的存在?

是否有可能编写一个模板,根据某个成员函数是否定义在类上而改变行为?

下面是我想写的一个简单的例子:

template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}

因此,如果class T定义了toString(),那么它将使用toString();否则,它就不会。我不知道如何做的神奇部分是“FUNCTION_EXISTS”部分。

215597 次浏览

这就是类型特征存在的意义。不幸的是,它们必须手动定义。在你的情况下,想象一下:

template <typename T>
struct response_trait {
static bool const has_tostring = false;
};


template <>
struct response_trait<your_type_with_tostring> {
static bool const has_tostring = true;
}

是的,使用SFINAE您可以检查给定的类是否提供了特定的方法。下面是工作代码:

#include <iostream>


struct Hello
{
int helloworld() { return 0; }
};


struct Generic {};


// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
struct two { char x[2]; };


template <typename C> static one test( decltype(&C::helloworld) ) ;
template <typename C> static two test(...);


public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
    

int main(int argc, char *argv[])
{
std::cout << has_helloworld<Hello>::value << std::endl;
std::cout << has_helloworld<Generic>::value << std::endl;
return 0;
}

我刚刚用Linux和gcc 4.1/4.3测试了它。我不知道它是否可以移植到运行不同编译器的其他平台。

现在这是一个不错的小谜题-好问题!

这里有一个替代尼古拉·博内利的解决方案的方法,它不依赖于非标准的typeof操作符。

不幸的是,它不能在GCC (MinGW) 3.4.5或Digital Mars 8.42n上工作,但它可以在所有版本的MSVC(包括VC6)和Comeau c++上工作。

较长的注释块有关于它如何工作(或应该如何工作)的详细信息。正如它所说,我不确定哪些行为符合标准-我欢迎对此发表评论。


更新- 2008年11月7日:

看起来,虽然这段代码在语法上是正确的,但MSVC和Comeau c++所显示的行为并不符合标准(感谢莱昂蒂默曼litb为我指明了正确的方向)。c++ 03标准说:

14.6.2依赖名称[temp.dep]

段3

在类模板的定义中 或类模板的成员,如果 类模板的基类 类型取决于模板参数 基类范围不检查 在非限定名称查找期间 在定义的时候 类的模板或成员 类模板的实例化或 成员。< / p >

因此,看起来当MSVC或Comeau考虑TtoString()成员函数在模板实例化时在doToString()的调用站点执行名称查找时,这是不正确的(尽管它实际上是我在本例中寻找的行为)。

GCC和Digital Mars的行为看起来是正确的——在这两种情况下,非成员toString()函数都绑定到调用。

老鼠-我以为我可能找到了一个聪明的解决方案,但我发现了几个编译器错误…


#include <iostream>
#include <string>


struct Hello
{
std::string toString() {
return "Hello";
}
};


struct Generic {};




// the following namespace keeps the toString() method out of
//  most everything - except the other stuff in this
//  compilation unit


namespace {
std::string toString()
{
return "toString not defined";
}


template <typename T>
class optionalToStringImpl : public T
{
public:
std::string doToString() {


// in theory, the name lookup for this call to
//  toString() should find the toString() in
//  the base class T if one exists, but if one
//  doesn't exist in the base class, it'll
//  find the free toString() function in
//  the private namespace.
//
// This theory works for MSVC (all versions
//  from VC6 to VC9) and Comeau C++, but
//  does not work with MinGW 3.4.5 or
//  Digital Mars 8.42n
//
// I'm honestly not sure what the standard says
//  is the correct behavior here - it's sort
//  of like ADL (Argument Dependent Lookup -
//  also known as Koenig Lookup) but without
//  arguments (except the implied "this" pointer)


return toString();
}
};
}


template <typename T>
std::string optionalToString(T & obj)
{
// ugly, hacky cast...
optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);


return temp->doToString();
}






int
main(int argc, char *argv[])
{
Hello helloObj;
Generic genericObj;


std::cout << optionalToString( helloObj) << std::endl;
std::cout << optionalToString( genericObj) << std::endl;
return 0;
}

c++允许使用SFINAE(注意,在c++ 11特性中,这更简单,因为它支持在几乎任意表达式上扩展SFINAE -下面的代码是为使用常见的c++ 03编译器而设计的):

#define HAS_MEM_FUNC(func, name)                                        \
template<typename T, typename Sign>                                 \
struct name {                                                       \
typedef char yes[1];                                            \
typedef char no [2];                                            \
template <typename U, U> struct type_check;                     \
template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
template <typename   > static no  &chk(...);                    \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
}

上面的模板和宏尝试实例化一个模板,给它一个成员函数指针类型,以及实际的成员函数指针。如果类型不匹配,SFINAE会导致模板被忽略。用法:

HAS_MEM_FUNC(toString, has_to_string);


template<typename T> void
doSomething() {
if(has_to_string<T, std::string(T::*)()>::value) {
...
} else {
...
}
}

但是请注意,您不能只在if分支中调用toString函数。由于编译器将在两个分支中检查有效性,因此在函数不存在的情况下会失败。一种方法是再次使用SFINAE (enable_if也可以从boost中获得):

template<bool C, typename T = void>
struct enable_if {
typedef T type;
};


template<typename T>
struct enable_if<false, T> { };


HAS_MEM_FUNC(toString, has_to_string);


template<typename T>
typename enable_if<has_to_string<T,
std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
/* something when T has toString ... */
return t->toString();
}


template<typename T>
typename enable_if<!has_to_string<T,
std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
/* something when T doesnt have toString ... */
return "T::toString() does not exist.";
}

享受使用它的乐趣。它的优点是它也适用于重载的成员函数,也适用于const成员函数(记得使用std::string(T::*)() const作为成员函数指针类型!)

如果方法恰好定义在基类中,那么这里由litb提供的标准c++解决方案将不能像预期的那样工作。

处理这种情况的解决方案请参考:

# EYZ0 < / p >

罗马翻译的英文。Perepelitsa: # EYZ0 < / p >

它非常聪明。然而,这种解决方案的一个问题是,如果被测试的类型不能用作基类(例如基本类型),则会给出编译器错误。

在Visual Studio中,我注意到如果使用没有参数的方法,则需要在参数周围插入一对额外的冗余()来在sizeof表达式中推导()。

奇怪的是,竟然没有人建议我在这个网站上看到的下面这个漂亮的把戏:

template <class T>
struct has_foo
{
struct S { void foo(...); };
struct derived : S, T {};


template <typename V, V> struct W {};


template <typename X>
char (&test(W<void (X::*)(), &X::foo> *))[1];


template <typename>
char (&test(...))[2];


static const bool value = sizeof(test<derived>(0)) == 1;
};

你必须确保T是一个类。查找foo时的模糊性似乎是替换失败。我让它在gcc上工作,但不确定它是否是标准的。

虽然这个问题是两年前的事了,但我敢补充我的答案。希望它能澄清之前无可争议的优秀解决方案。我采用了Nicola Bonelli和Johannes Schaub非常有用的答案,并将它们合并到一个解决方案中,以我之见,更易于阅读,清晰,不需要typeof扩展:

template <class Type>
class TypeHasToString
{
// This type won't compile if the second template parameter isn't of type T,
// so I can put a function pointer type in the first parameter and the function
// itself in the second thus checking that the function has a specific signature.
template <typename T, T> struct TypeCheck;


typedef char Yes;
typedef long No;


// A helper struct to hold the declaration of the function pointer.
// Change it if the function signature changes.
template <typename T> struct ToString
{
typedef void (T::*fptr)();
};


template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
template <typename T> static No  HasToString(...);


public:
static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};
我用gcc 4.1.2检查了它。 这主要归功于Nicola Bonelli和Johannes Schaub,所以如果我的回答对你有帮助,请给他们投票:)

MSVC有__if_exists和__if_not_exists关键字(医生)。连同Nicola的typef - sfinae方法,我可以创建一个检查GCC和MSVC,就像OP所寻找的那样。

来源可以找到在这里

下面是一些用法片段: *所有这些的核心都在下面

在给定的类中检查成员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;

检查成员enum 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;
};

# EYZ0 < / >强

CREATE_MEMBER_CHECK:

//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                                                            \
;                                                                       \
}

CREATE_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 {}

CREATE_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 {}

CREATE_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 {}

CREATE_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 {}

CREATE_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 {}

CREATE_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_MEMBER_CHECKS:

//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)

我在另一个线程中对此写了一个答案(与上面的解决方案不同),也检查继承的成员函数:

SFINAE检查继承的成员函数

以下是该解决方案的一些例子:

例二:

我们正在检查具有以下签名的成员: # EYZ0 < / p >

template<class T> struct has_const_begin
{
typedef char (&Yes)[1];
typedef char (&No)[2];


template<class U>
static Yes test(U const * data,
typename std::enable_if<std::is_same<
typename U::const_iterator,
decltype(data->begin())
>::value>::type * = 0);
static No test(...);
static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

请注意,它甚至检查方法的常量,并且也适用于基本类型。(我的意思是has_const_begin<int>::value是假的,不会导致编译时错误。)

示例2

现在我们正在寻找签名:void foo(MyClass&, unsigned)

template<class T> struct has_foo
{
typedef char (&Yes)[1];
typedef char (&No)[2];


template<class U>
static Yes test(U * data, MyClass* arg1 = 0,
typename std::enable_if<std::is_void<
decltype(data->foo(*arg1, 1u))
>::value>::type * = 0);
static No test(...);
static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};

请注意,MyClass不一定是默认可构造的或满足任何特殊的概念。该技术也适用于模板成员。

我急切地等待有关这方面的意见。

这个问题很老了,但是在c++ 11中,我们有了一种新的方法来检查函数是否存在(或者任何非类型成员是否存在),再次依赖SFINAE:

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
-> decltype(os << obj, void())
{
os << obj;
}


template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
-> decltype(obj.stream(os), void())
{
obj.stream(os);
}


template<class T>
auto serialize(std::ostream& os, T const& obj)
-> decltype(serialize_imp(os, obj, 0), void())
{
serialize_imp(os, obj, 0);
}

现在来解释一下。首先,我使用表达SFINAE从重载解析中排除serialize(_imp)函数,如果decltype内的第一个表达式无效(即函数不存在)。

void()用于将所有这些函数的返回类型设置为void

0参数用于在两者都可用的情况下选择os << obj重载(字面量0的类型是int,因此第一个重载是更好的匹配)。


现在,您可能需要一个trait来检查函数是否存在。幸运的是,这很容易写出来。但是请注意,您需要为每个可能需要的不同函数名编写trait 你自己

#include <type_traits>


template<class>
struct sfinae_true : std::true_type{};


namespace detail{
template<class T, class A0>
static auto test_stream(int)
-> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
template<class, class A0>
static auto test_stream(long) -> std::false_type;
} // detail::


template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

活例

接下来是解释。首先,sfinae_true是一个帮助器类型,它基本上等同于编写decltype(void(std::declval<T>().stream(a0)), std::true_type{})。优点很简单,它更短 接下来,struct has_stream : decltype(...)最终继承了std::true_typestd::false_type,这取决于test_stream中的decltype检查是否失败 最后,std::declval为您提供了传递的任何类型的“值”,而不需要知道如何构造它。注意,这只可能在一个未求值的上下文中,例如decltypesizeof和其他


注意,decltype不一定是必需的,因为sizeof(以及所有未求值的上下文)得到了增强。只是decltype已经交付了一个类型,因此更加简洁。下面是其中一个重载的sizeof版本:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
int(*)[sizeof((os << obj),0)] = 0)
{
os << obj;
}

由于同样的原因,intlong参数仍然存在。数组指针用于提供可以使用sizeof的上下文。

这个解决方案怎么样?

#include <type_traits>


template <typename U, typename = void> struct hasToString : std::false_type { };


template <typename U>
struct hasToString<U,
typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };

我修改了https://stackoverflow.com/a/264088/2712152中提供的解决方案,使其更加通用。此外,由于它不使用任何新的c++ 11特性,我们可以将它与旧的编译器一起使用,并且应该也可以与msvc一起使用。但是编译器应该允许C99使用这个,因为它使用可变宏。

下面的宏可用于检查特定类是否具有特定类型定义。

/**
* @class      : HAS_TYPEDEF
* @brief      : This macro will be used to check if a class has a particular
* typedef or not.
* @param typedef_name : Name of Typedef
* @param name  : Name of struct which is going to be run the test for
* the given particular typedef specified in typedef_name
*/
#define HAS_TYPEDEF(typedef_name, name)                           \
template <typename T>                                          \
struct name {                                                  \
typedef char yes[1];                                        \
typedef char no[2];                                         \
template <typename U>                                       \
struct type_check;                                          \
template <typename _1>                                      \
static yes& chk(type_check<typename _1::typedef_name>*);    \
template <typename>                                         \
static no& chk(...);                                        \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}

下面的宏可以用来检查一个特定的类是否有一个特定的成员函数,是否有给定数量的参数。

/**
* @class      : HAS_MEM_FUNC
* @brief      : This macro will be used to check if a class has a particular
* member function implemented in the public section or not.
* @param func : Name of Member Function
* @param name : Name of struct which is going to be run the test for
* the given particular member function name specified in func
* @param return_type: Return type of the member function
* @param ellipsis(...) : Since this is macro should provide test case for every
* possible member function we use variadic macros to cover all possibilities
*/
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
template <typename T>                                          \
struct name {                                                  \
typedef return_type (T::*Sign)(__VA_ARGS__);                \
typedef char yes[1];                                        \
typedef char no[2];                                         \
template <typename U, U>                                    \
struct type_check;                                          \
template <typename _1>                                      \
static yes& chk(type_check<Sign, &_1::func>*);              \
template <typename>                                         \
static no& chk(...);                                        \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}

我们可以使用上面的两个宏来检查has_typedef和has_mem_func:

class A {
public:
typedef int check;
void check_function() {}
};


class B {
public:
void hello(int a, double b) {}
void hello() {}
};


HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);


int main() {
std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}

下面是我的版本,它可以任意处理所有可能的成员函数重载,包括模板成员函数,可能带有默认实参。当成员函数调用某个类类型时,它区分了3种互斥的情况,给定的arg类型:(1)有效,或(2)模糊,或(3)不可用。使用示例:

#include <string>
#include <vector>


HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)


struct test
{
void bar(int);
void bar(double);
void bar(int,double);


template < typename T >
typename std::enable_if< not std::is_integral<T>::value >::type
bar(const T&, int=0){}


template < typename T >
typename std::enable_if< std::is_integral<T>::value >::type
bar(const std::vector<T>&, T*){}


template < typename T >
int bar(const std::string&, int){}
};

现在你可以这样使用它:

int main(int argc, const char * argv[])
{
static_assert( has_mem_bar<test>::value , "");


static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");


static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");


static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");


static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");


static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");


static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");


static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");


return 0;
}

下面是用c++11编写的代码,但是,你可以很容易地将它移植到具有typeof扩展的非c++11(例如gcc)。你可以用你自己的宏替换HAS_MEM宏。

#pragma once


#if __cplusplus >= 201103


#include <utility>
#include <type_traits>


#define HAS_MEM(mem)                                                                                     \
\
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
struct yes {};                                                                                     \
struct no  {};                                                                                     \
\
struct ambiguate_seed { char mem; };                                                               \
template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
\
template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
template < typename                                 > static constexpr yes test(...);              \
\
static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
typedef std::integral_constant<bool,value>    type;                                                \
};




#define HAS_MEM_FUN_CALL(memfun)                                                                         \
\
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
\
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
struct yes {};                                                                                     \
struct no  {};                                                                                     \
\
template < typename U, bool = has_mem_##memfun<U>::value >                                         \
struct impl                                                                                        \
{                                                                                                  \
template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
struct test_result { using type = yes; };                                                       \
\
template < typename V > static constexpr typename test_result<V>::type test(int);               \
template < typename   > static constexpr                            no test(...);               \
\
static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
using type = std::integral_constant<bool, value>;                                               \
};                                                                                                 \
\
template < typename U >                                                                            \
struct impl<U,false> : std::false_type {};                                                         \
\
static constexpr bool value = impl<T>::value;                                                      \
using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
\
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
\
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
struct ambiguate_seed { void memfun(...); };                                                       \
\
template < class U, bool = has_mem_##memfun<U>::value >                                            \
struct ambiguate : U, ambiguate_seed                                                               \
{                                                                                                  \
using ambiguate_seed::memfun;                                                                    \
using U::memfun;                                                                                 \
};                                                                                                 \
\
template < class U >                                                                               \
struct ambiguate<U,false> : ambiguate_seed {};                                                     \
\
static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
\
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
\
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
\
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
\
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
\
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
\
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};


#endif

c++ 20 - requires表达式

c++ 20带来了一些概念和各种工具,比如# EYZ0表达式,这是一种内置的检查函数是否存在的方法。用它们你可以重写你的optionalToString函数,如下所示:

template<class T>
std::string optionalToString(T* obj)
{
constexpr bool has_toString = requires(const T& t) {
t.toString();
};


if constexpr (has_toString)
return obj->toString();
else
return "toString not defined";
}

pre - c++ 20 -检测工具包

N4502提出了一个包含在c++ 17标准库中的检测工具包,该标准库最终成为标准库基础TS v2。它很可能永远不会进入标准,因为它已经被requires表达式所包含,但它仍然以一种优雅的方式解决了问题。该工具包引入了一些元函数,包括std::is_detected,可用于在其顶部轻松编写类型或函数检测元函数。下面是你如何使用它:

template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );


template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

注意,上面的例子是未经测试的。标准库中还没有检测工具包,但建议包含了一个完整的实现,如果您确实需要它,可以很容易地复制它。它与c++ 17特性if constexpr一起很好地发挥作用:

template<class T>
std::string optionalToString(T* obj)
{
if constexpr (has_toString<T>)
return obj->toString();
else
return "toString not defined";
}

c++ 14 - Boost。刘荷娜

提振。Hana显然建立在这个特定的例子之上,并在其文档中提供了c++ 14的解决方案,所以我将直接引用它:

[…Hana提供了一个is_valid函数,它可以与c++ 14通用的lambdas相结合,以获得一个更清晰的实现:

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

这样我们就得到了一个函数对象has_toString,它返回给定表达式在传递给它的参数上是否有效。返回的结果是IntegralConstant,因此常量性在这里不是问题,因为函数的结果无论如何都表示为类型。现在,除了减少啰嗦(只有一行代码!)之外,意图也更加清晰了。其他好处是has_toString可以传递给更高阶的算法,也可以在函数范围内定义它,因此不需要用实现细节污染名称空间范围。

提振。创科实业

执行这种检查的另一个惯用的工具包是提振。创科实业,在Boost 1.5.4中引入。对于您的示例,您必须使用宏BOOST_TTI_HAS_MEMBER_FUNCTION。下面是你如何使用它:

#include <boost/tti/has_member_function.hpp>


// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)


// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

然后,您可以使用bool来创建SFINAE检查。

解释

BOOST_TTI_HAS_MEMBER_FUNCTION生成元函数has_member_function_toString,它将选中的类型作为它的第一个模板参数。第二个模板形参对应于成员函数的返回类型,下面的形参对应于函数形参的类型。如果类T有一个成员函数std::string toString(),则成员value包含true

或者,has_member_function_toString可以接受一个成员函数指针作为模板形参。因此,可以将has_member_function_toString<T, std::string>::value替换为has_member_function_toString<std::string T::* ()>::value

这是一个c++ 11的解决方案,用于解决“如果我做X,它会编译吗?”

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
T,
type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};

Trait has_to_string使得has_to_string<T>::valuetrue当且仅当T有一个方法.toString,在这个上下文中可以用0参数调用。

接下来,我将使用标签调度:

namespace details {
template<class T>
std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
return obj->toString();
}
template<class T>
std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
return "toString not defined";
}
}
template<class T>
std::string optionalToString(T* obj) {
return details::optionalToString_helper( obj, has_to_string<T>{} );
}

它比复杂的SFINAE表达式更易于维护。

如果你发现自己经常这样做,你可以用宏来写这些特征,但它们相对简单(每个只有几行),所以可能不值得这样做:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

上面所做的是创建一个宏MAKE_CODE_TRAIT。您向它传递您想要的trait的名称,以及一些可以测试类型T的代码。因此:

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

创建上述特征类。

作为题外话,上面的技术是MS所谓的“表达式SFINAE”的一部分,他们的2013编译器失败相当严重。

注意,在c++ 1y中,以下语法是可能的:

template<class T>
std::string optionalToString(T* obj) {
return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
return obj.toString();
}) *compiled_else ([&]{
return "toString not defined";
});
}

这是一个内联编译条件分支,滥用了大量c++特性。这样做可能是不值得的,因为(代码内联的)好处不值得付出代价(几乎没有人理解它是如何工作的),但是上述解决方案的存在可能会引起人们的兴趣。

好吧,这个问题已经有一长串的答案了,但是我想强调一下Morwenn的评论:c++ 17有一个提案让它变得非常简单。有关详细信息,请参阅N4502,但作为一个自包含的示例,请考虑以下内容。

这部分是常量部分,放在头文件中。

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;


// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};


// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

然后是变量部分,在此指定要查找的内容(类型、成员类型、函数、成员函数等)。就OP而言:

template <typename T>
using toString_t = decltype(std::declval<T>().toString());


template <typename T>
using has_toString = detect<T, toString_t>;

下面的例子取自N4502,展示了一个更精细的探测:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())


// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

与上面描述的其他实现相比,这个实现相当简单:减少了工具集(void_tdetect)就足够了,不需要复杂的宏。此外,据报道(见N4502),它比以前的方法更有效(编译时间和编译器内存消耗)。

这是生活的例子。它可以很好地与Clang一起工作,但不幸的是,5.1之前的GCC版本遵循了对c++ 11标准的不同解释,这导致void_t不能按预期工作。Yakk已经提供了解决方案:使用以下定义的void_t (参数列表中的Void_t工作,但不能作为返回类型):

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif

c++ 11的一个简单解决方案:

template<class T>
auto optionalToString(T* obj)
-> decltype(  obj->toString()  )
{
return     obj->toString();
}
auto optionalToString(...) -> string
{
return "toString not defined";
}

更新,3年后:(这是未经测试的)。为了检验是否存在,我认为这是可行的:

template<class T>
constexpr auto test_has_toString_method(T* obj)
-> decltype(  obj->toString() , std::true_type{} )
{
return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
return "toString not defined";
}
这里有很多答案,但我没有找到一个版本,执行真正的方法解析排序,而不使用任何更新的c++特性(只使用c++98特性) 注意:此版本已测试,并使用vc++2013, g++ 5.2.0和在线编译器

所以我提出了一个版本,只使用sizeof():

template<typename T> T declval(void);


struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);


struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};


template<typename T>
struct has_awesome_member {
template<typename U> static yes_no<(sizeof((
declval<U>().awesome_member(),fake_void()
))!=0)> check(int);
template<typename> static no check(...);
enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};




struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };


static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

现场演示(带有扩展的返回类型检查和vc++2010解决方案):http://cpp.sh/5b2vs

没有消息来源,因为是我自己想出来的。

当在g++编译器上运行Live演示时,请注意数组大小为0是允许的,这意味着使用static_assert将不会触发编译器错误,即使它失败了 一个常用的解决方法是将宏中的'typedef'替换为'extern'

你可以跳过c++ 14中所有的元编程,只使用适合库中的fit::conditional来编写:

template<class T>
std::string optionalToString(T* x)
{
return fit::conditional(
[](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
[](auto*) { return "toString not defined"; }
)(x);
}

你也可以直接从lambdas中创建函数:

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
[](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
[](auto*) -> std::string { return "toString not defined"; }
);

然而,如果你使用的编译器不支持泛型lambdas,你将不得不编写单独的函数对象:

struct withToString
{
template<class T>
auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
{
return obj->toString();
}
};


struct withoutToString
{
template<class T>
std::string operator()(T*) const
{
return "toString not defined";
}
};


FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
withToString(),
withoutToString()
);

泛型模板,用于检查类型是否支持某些“特性”:

#include <type_traits>


template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
// these structs are used to recognize which version
// of the two functions was chosen during overload resolution
struct supported {};
struct not_supported {};


// this overload of chk will be ignored by SFINAE principle
// if TypeChecker<Type_> is invalid type
template <typename Type_>
static supported chk(typename std::decay<TypeChecker<Type_>>::type *);


// ellipsis has the lowest conversion rank, so this overload will be
// chosen during overload resolution only if the template overload above is ignored
template <typename Type_>
static not_supported chk(...);


// if the template overload of chk is chosen during
// overload resolution then the feature is supported
// if the ellipses overload is chosen the the feature is not supported
static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};

检查是否有一个方法foo与签名double(const char*)兼容的模板

// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));

例子

// types that support has_foo
struct struct1 { double foo(const char*); };            // exact signature match
struct struct2 { int    foo(const std::string &str); }; // compatible signature
struct struct3 { float  foo(...); };                    // compatible ellipsis signature
struct struct4 { template <typename T>
int    foo(T t); };                    // compatible template signature


// types that do not support has_foo
struct struct5 { void        foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double      foo(      int *); }; // const char* can't be converted to int*
struct struct8 { double      bar(const char*); }; // there is no foo method


int main()
{
std::cout << std::boolalpha;


std::cout << is_supported<has_foo, int    >::value << std::endl; // false
std::cout << is_supported<has_foo, double >::value << std::endl; // false


std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
std::cout << is_supported<has_foo, struct4>::value << std::endl; // true


std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
std::cout << is_supported<has_foo, struct8>::value << std::endl; // false


return 0;
}

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4

下面是工作代码的示例。

template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());


template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
return obj->toString();
}


template <class T>
std::string optionalToString(const T* obj, long)
{
return "toString not defined";
}


int main()
{
A* a;
B* b;


std::cout << optionalToString(a, 0) << std::endl; // This is A
std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}

toStringFn<T>* = nullptr将启用接受额外的int参数的函数,该函数在使用0调用时优先于接受long参数的函数。

你可以对函数使用相同的原则,如果函数被实现,返回true

template <typename T>
constexpr bool toStringExists(long)
{
return false;
}


template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
return true;
}




int main()
{
A* a;
B* b;


std::cout << toStringExists<A>(0) << std::endl; // true
std::cout << toStringExists<B>(0) << std::endl; // false
}

一个使用SFINAE和模板部分特化的例子,通过编写Has_foo概念检查:

#include <type_traits>
struct A{};


struct B{ int foo(int a, int b);};


struct C{void foo(int a, int b);};


struct D{int foo();};


struct E: public B{};


// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;


template<typename T, typename = void> struct Has_foo: std::false_type{};


template<typename T>
struct Has_foo<T, void_t<
std::enable_if_t<
std::is_same<
int,
decltype(std::declval<T>().foo((int)0, (int)0))
>::value
>
>>: std::true_type{};




static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");

我也遇到过类似的问题:

一个模板类,可以从少数基类派生,其中一些基类具有某个成员,而另一些基类没有。

我解决它类似于“typeof”(Nicola Bonelli)的答案,但使用decltype,所以它在MSVS上编译和正确运行:

#include <iostream>
#include <string>


struct Generic {};
struct HasMember
{
HasMember() : _a(1) {};
int _a;
};


// SFINAE test
template <typename T>
class S : public T
{
public:
std::string foo (std::string b)
{
return foo2<T>(b,0);
}


protected:
template <typename T> std::string foo2 (std::string b, decltype (T::_a))
{
return b + std::to_string(T::_a);
}
template <typename T> std::string foo2 (std::string b, ...)
{
return b + "No";
}
};


int main(int argc, char *argv[])
{
S<HasMember> d1;
S<Generic> d2;


std::cout << d1.foo("HasMember: ") << std::endl;
std::cout << d2.foo("Generic: ") << std::endl;
return 0;
}
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
return obj->toString();
}


template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
throw "Error!";
}

用c++ 20你可以写以下代码:

template<typename T>
concept has_toString = requires(const T& t) {
t.toString();
};


template<typename T>
std::string optionalToString(const T& obj)
{
if constexpr (has_toString<T>)
return obj.toString();
else
return "toString not defined";
}

这是c++ 17中的另一种方法(受boost:hana的启发)。

这个解决方案不需要has_something<T> SFINAE类型特征类。

解决方案

////////////////////////////////////////////
// has_member implementation
////////////////////////////////////////////


#include <type_traits>


template<typename T, typename F>
constexpr auto has_member_impl(F&& f) -> decltype(f(std::declval<T>()), true)
{
return true;
}


template<typename>
constexpr bool has_member_impl(...) { return false; }


#define has_member(T, EXPR) \
has_member_impl<T>( [](auto&& obj)->decltype(obj.EXPR){} )


测试

////////////////////////////////////////////
// Test
////////////////////////////////////////////


#include <iostream>
#include <string>


struct Example {
int Foo;
void Bar() {}
std::string toString() { return "Hello from Example::toString()!"; }
};


struct Example2 {
int X;
};


template<class T>
std::string optionalToString(T* obj)
{
if constexpr(has_member(T, toString()))
return obj->toString();
else
return "toString not defined";
}


int main() {
static_assert(has_member(Example, Foo),
"Example class must have Foo member");
static_assert(has_member(Example, Bar()),
"Example class must have Bar() member function");
static_assert(!has_member(Example, ZFoo),
"Example class must not have ZFoo member.");
static_assert(!has_member(Example, ZBar()),
"Example class must not have ZBar() member function");


Example e1;
Example2 e2;


std::cout << "e1: " << optionalToString(&e1) << "\n";
std::cout << "e1: " << optionalToString(&e2) << "\n";
}

我的观点是:在不为每一个都创建冗长的类型特征,或使用实验特性或长代码的情况下,普遍地确定某个东西是否可调用:

template<typename Callable, typename... Args, typename = decltype(declval<Callable>()(declval<Args>()...))>
std::true_type isCallableImpl(Callable, Args...) { return {}; }


std::false_type isCallableImpl(...) { return {}; }


template<typename... Args, typename Callable>
constexpr bool isCallable(Callable callable) {
return decltype(isCallableImpl(callable, declval<Args>()...)){};
}

用法:

constexpr auto TO_STRING_TEST = [](auto in) -> decltype(in.toString()) { return {}; };
constexpr bool TO_STRING_WORKS = isCallable<T>(TO_STRING_TEST);

我知道这个问题已经存在多年了,但我认为对于像我这样的人来说,有一个更完整的更新的答案也适用于const重载方法(如std::vector<>::begin)将是有用的。

基于我接下来问题中的回答回答,这里有一个更完整的答案。注意,这只适用于c++ 11及更高版本。

#include <iostream>
#include <vector>


class EmptyClass{};


template <typename T>
class has_begin
{
private:
has_begin() = delete;
    

struct one { char x[1]; };
struct two { char x[2]; };


template <typename C> static one test( decltype(void(std::declval<C &>().begin())) * ) ;
template <typename C> static two test(...);


public:
static constexpr bool value = sizeof(test<T>(0)) == sizeof(one);
};
    

int main(int argc, char *argv[])
{
std::cout << std::boolalpha;
std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
std::cout << "EmptyClass::begin() exists: " << has_begin<EmptyClass>::value << std::endl;
return 0;
}

或者更短的版本:

#include <iostream>
#include <vector>


class EmptyClass{};


template <typename T, typename = void>
struct has_begin : std::false_type {};


template <typename T>
struct has_begin<T, decltype(void(std::declval<T &>().begin()))> : std::true_type {};


int main(int argc, char *argv[])
{
std::cout << std::boolalpha;
std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
std::cout << "EmptyClass exists: " << has_begin<EmptyClass>::value << std::endl;
}

注意,这里必须提供一个完整的示例调用。这意味着如果我们测试了resize方法的存在性,那么我们将放置resize(0)

# EYZ0:

这个问题的第一个答案使用了test( decltype(&C::helloworld) );然而,当它所测试的方法由于const重载而模棱两可时,这就有问题了,从而导致替换尝试失败。

为了解决这种模糊性,我们使用一个void语句,它可以接受任何参数,因为它总是被转换为noop,因此模糊性是无效的,并且只要方法存在,调用就有效:

has_begin<T, decltype(void(std::declval<T &>().begin()))>

以下是发生的顺序: 我们使用std::declval<T &>()创建一个可调用值,然后可以调用begin。之后,begin的值作为参数传递给void语句。然后,我们使用内置的decltype检索该void表达式的类型,以便它可以用作模板类型参数。如果begin不存在,则替换无效,根据SFINAE,将使用其他声明代替

可能不像其他例子那么好,但这是我为c++ 11想出的。这适用于选择重载方法。

template <typename... Args>
struct Pack {};


#define Proxy(T) ((T &)(*(int *)(nullptr)))


template <typename Class, typename ArgPack, typename = nullptr_t>
struct HasFoo
{
enum { value = false };
};


template <typename Class, typename... Args>
struct HasFoo<
Class,
Pack<Args...>,
decltype((void)(Proxy(Class).foo(Proxy(Args)...)), nullptr)>
{
enum { value = true };
};

示例使用

struct Object
{
int foo(int n)         { return n; }
#if SOME_CONDITION
int foo(int n, char c) { return n + c; }
#endif
};


template <bool has_foo_int_char>
struct Dispatcher;


template <>
struct Dispatcher<false>
{
template <typename Object>
static int exec(Object &object, int n, char c)
{
return object.foo(n) + c;
}
};


template <>
struct Dispatcher<true>
{
template <typename Object>
static int exec(Object &object, int n, char c)
{
return object.foo(n, c);
}
};


int runExample()
{
using Args = Pack<int, char>;
enum { has_overload = HasFoo<Object, Args>::value };
Object object;
return Dispatcher<has_overload>::exec(object, 100, 'a');
}

我一直在寻找一个方法,允许以某种方式不绑定结构名has_member到类成员的名称。 实际上,如果lambda可以被允许在未求值的表达式中(这是标准禁止的),这将更简单,即has_member<ClassName, SOME_MACRO_WITH_DECLTYPE(member_name)>

#include <iostream>
#include <list>
#include <type_traits>


#define LAMBDA_FOR_MEMBER_NAME(NAME) [](auto object_instance) -> decltype(&(decltype(object_instance)::NAME)) {}


template<typename T>
struct TypeGetter
{
constexpr TypeGetter() = default;
constexpr TypeGetter(T) {}
using type = T;


constexpr auto getValue()
{
return std::declval<type>();
}
};


template<typename T, typename LambdaExpressionT>
struct has_member {
using lambda_prototype = LambdaExpressionT;


//SFINAE
template<class ValueT, class = void>
struct is_void_t_deducable : std::false_type {};


template<class ValueT>
struct is_void_t_deducable<ValueT,
std::void_t<decltype(std::declval<lambda_prototype>()(std::declval<ValueT>()))>> : std::true_type {};


static constexpr bool value = is_void_t_deducable<T>::value;
};


struct SimpleClass
{
int field;
void method() {}
};


int main(void)
{
const auto helpful_lambda = LAMBDA_FOR_MEMBER_NAME(field);
using member_field = decltype(helpful_lambda);
std::cout << has_member<SimpleClass, member_field>::value;


const auto lambda = LAMBDA_FOR_MEMBER_NAME(method);
using member_method = decltype(lambda);
std::cout << has_member<SimpleClass, member_method>::value;
    

}

这是我在c++ 20中发现的最简洁的方法,非常接近你的问题:

template<class T>
std::string optionalToString(T* obj)
{
if constexpr (requires { obj->toString(); })
return obj->toString();
else
return "toString not defined";
}

在godbolt上看到它:https://gcc.godbolt.org/z/5jb1d93Ms

pre -c++20,简单用例的简单选项:

如果你知道你的类是默认可构造的,我们可以使语法更简单。

我们将从最简单的情况开始:默认可构造对象,并且我们知道预期的返回类型。实例方法:

int foo ();

我们可以不使用declval来编写类型特征:

template <auto v>
struct tag_v
{
constexpr static auto value = v;
};


template <class, class = int>
struct has_foo_method : tag_v<false> {};


template <class T>
struct has_foo_method <T, decltype(T().foo())>
: tag_v<true> {};

demo

注意,我们将默认类型设置为int,因为这是foo的返回类型。

如果有多个可接受的返回类型,那么我们添加第二个参数到decltype,它与默认类型相同,覆盖第一个参数:

decltype(T().foo(), int())

demo

(这里的int类型不重要-我使用它是因为它只有3个字母)