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

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

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

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


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

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测试了它。我不知道它是否可以移植到运行不同编译器的其他平台。


这是个不错的小难题——好问题!

这里有一个替代Nicola Bonelli的解决方案,它不依赖于非标准typeof运算符。

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

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


更新- 2008年11月7日:

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

14.6.2依赖名称[temp.dep] 段3 在类模板定义中 或类模板的成员,如果 类模板的基类 类型取决于模板参数 基类范围不检查 在非限定名称查找期间 在定义的时候 类的模板或成员 类模板的实例化或 成员。

因此,当MSVC或Comeau考虑T的toString()成员函数在模板实例化时在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++解决方案将不能像预期的那样工作。

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

俄语: http://www.rsdn.ru/forum/message/2759773.1.aspx

由罗马人翻译的英文。Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

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

在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非常有用的答案,并将它们合并到一个解决方案中,恕我之言,这个解决方案更易于阅读,更清晰,不需要扩展类型:

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检查了它。 这主要归功于尼古拉·博内利和约翰内斯·绍布,如果我的回答对你有帮助,请给他们投票:)


MSVC有__if_exists和__if_not_exists关键字(Doc)。连同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;

OR

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

宏(El Diablo!):

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检查继承的成员函数

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

例二:

我们正在检查一个具有以下签名的成员: T::const_iterator begin(

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为false,不会导致编译时错误。)

示例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从重载解析中排除序列化(_imp)函数,如果decltype中的第一个表达式无效(即函数不存在)。

void()用于使所有这些函数的返回类型为空。

如果os << obj重载都可用,则使用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)){};

生活的例子。

And on to explanations. First, sfinae_true is a helper type, and it basically amounts to the same as writing decltype(void(std::declval<T>().stream(a0)), std::true_type{}). The advantage is simply that it's shorter. Next, the struct has_stream : decltype(...) inherits from either std::true_type or std::false_type in the end, depending on whether the decltype check in test_stream fails or not. Last, std::declval gives you a "value" of whatever type you pass, without you needing to know how you can construct it. Note that this is only possible inside an unevaluated context, such as decltype, sizeof and others.


注意,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;
}

由于同样的原因,int和long形参仍然存在。数组指针用于提供可以使用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 -需要表达式

c++ 20带来了一些概念和各种工具,比如require表达式,这是一种检查函数是否存在的内置方式。有了它们,你可以重写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 proposes a detection toolkit for inclusion into the C++17 standard library that eventually made it into the library fundamentals TS v2. It most likely won't ever get into the standard because it has been subsumed by requires expressions since, but it still solves the problem in a somewhat elegant manner. The toolkit introduces some metafunctions, including std::is_detected which can be used to easily write type or function detection metafunctions on the top of it. Here is how you could use it:

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的特性,如果constexpr:

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

C++14 - 助推哈娜

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

[...] Hana provides a is_valid function that can be combined with C++14 generic lambdas to obtain a much cleaner implementation of the same thing: auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { }); This leaves us with a function object has_toString which returns whether the given expression is valid on the argument we pass to it. The result is returned as an IntegralConstant, so constexpr-ness is not an issue here because the result of the function is represented as a type anyway. Now, in addition to being less verbose (that's a one liner!), the intent is much clearer. Other benefits are the fact that has_toString can be passed to higher order algorithms and it can also be defined at function scope, so there is no need to pollute the namespace scope with implementation details.

提振。创科实业

执行这种检查的另一个惯用工具包是Boost,尽管它没有那么优雅。TTI,在Boost 1.54 4.0中引入。对于您的示例,您必须使用宏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(),则成员值为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>::value为true当且仅当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_t和detect)就足够了,不需要复杂的宏。此外,据报道(参见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库中使用fit::条件来编写:

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是否与signature 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调用时需要很长时间的函数。

你可以对函数使用相同的原则,如果函数被实现,返回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类型trait类。

解决方案

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

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

深魔解释:

这个问题的第一个答案使用了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的类型trait:

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个字母)