关于C++:有可能编写一个模板来检查函数的存在吗?

Is it possible to write a template to check for a function's existence?

是否可以编写一个更改行为的模板,这取决于是否在类上定义了某个成员函数?

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

1
2
3
4
5
6
7
8
template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return"toString not defined";
}

所以,如果class T定义了toString(),那么它就使用它;否则,它就不使用了。我不知道该怎么做的魔法部分是"功能存在"部分。


是的,使用sfinae,您可以检查给定的类是否提供了某种方法。工作代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

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

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    typedef long two;

    template <typename C> static one test( typeof(&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和GCC4.1/4.3测试过它。我不知道它是否可以移植到运行不同编译器的其他平台上。


这个问题是旧的,但是用C++ 11,我们得到了一个新的方法来检查函数的存在(或者任何非类型成员的存在,真的),依赖于sFANAE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
}

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

void()用于生成所有这些函数void的返回类型。

如果两个参数都可用,则使用0参数来选择os << obj重载(文字0int类型,因此第一个重载是更好的匹配)。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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().stream(a0)), std::true_type{})一样。优点就是它比较短。接下来,根据test_stream中的decltype检查是否失败,struct has_stream : decltype(...)最终从std::true_typestd::false_type继承。最后,std::declval为您提供了任何类型的"值",而不需要知道如何构造它。请注意,这只能在未评估的上下文中进行,如decltypesizeof和其他上下文。

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

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

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


C++允许SfENA用于此(注意C++ 11的特点,这是简单的,因为它支持几乎任意表达式的扩展sFANE——下面是用普通C++ 03编译器编写的):

1
2
3
4
5
6
7
8
9
10
#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会导致忽略模板。用法如下:

1
2
3
4
5
6
7
8
9
10
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(如果也可以从boost获得,则启用u):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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.";
}

玩得开心。它的优点是它也适用于重载的成员函数,也适用于常量成员函数(请记住使用std::string(T::*)() const作为成员函数指针类型,然后!).


尽管这个问题已经两年了,我还是敢补充我的答案。希望它能澄清之前无可争议的优秀解决方案。我从尼古拉·博内利和约翰内斯·肖布那里得到了非常有用的答案,并将它们合并成一个解决方案,即,imho,更易读、更清晰,不需要typeof扩展名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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核对过。这主要归功于尼古拉·博内利和约翰内斯·朔布,所以如果我的回答对你有帮助的话,就给他们投票吧:)


C++ 20—EDOCX1·0表达式

用C++ 20来生成概念和各种工具,如EDCOX1、0、表达式,这些表达式是检查函数存在的一种内置方法。有了tehm,您可以重写optionalToString函数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
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接受。它引入了一些元函数,包括std::is_detected,这些元函数可以很容易地在其顶部写入类型或函数检测元函数。以下是您可以使用它的方法:

1
2
3
4
5
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的特性EDOCX1,4表示很好:

1
2
3
4
5
6
7
8
template<class T>
std::string optionalToString(T* obj)
{
    if constexpr (has_toString<T>)
        return obj->toString();
    else
        return"toString not defined";
}

助听器

另一个执行这种检查的惯用工具包是boost.tti,在boost 1.54.0中引入。对于您的示例,您必须使用宏BOOST_TTI_HAS_MEMBER_FUNCTION。以下是您可以使用它的方法:

1
2
3
4
5
6
7
8
#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::value代替has_member_function_toString::value


这就是它们的类型特征。不幸的是,它们必须手动定义。在您的案例中,请想象以下情况:

1
2
3
4
5
6
7
8
9
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;
}


C++ 11的简单解决方案:

1
2
3
4
5
6
7
8
9
10
template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return"toString not defined";
}

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

1
2
3
4
5
6
7
8
9
10
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";
}


这个问题已经有很长的答案了,但是我想强调一下MMOWN的评论:有一个C++ 17的建议,使它更简单。有关详细信息,请参见N4502,但作为一个独立的示例,请考虑以下内容。

这部分是常量部分,放在标题中。

1
2
3
4
5
6
7
8
9
10
11
// 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:

1
2
3
4
5
template <typename T>
using toString_t = decltype(std::declval<T>().toString());

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

下面的例子取自N4502,展示了一个更复杂的探针:

1
2
3
4
5
6
7
// 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标准的不同解释,这导致EDCOX1 0不按预期工作。Yakk已经提供了解决方案:使用void_t的以下定义(参数列表中的void_t工作,但不作为返回类型):

1
2
3
4
5
6
7
8
9
10
11
12
13
#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解决一般问题的方法,"如果我做了X,它会编译吗?"

1
2
3
4
5
6
7
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 {};

特征has_to_string,使得has_to_string::valuetrue,前提是并且仅当T有一个方法.toString时,在这种情况下可以用0个参数调用。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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表达式更易于维护。

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

1
2
3
#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。你给它传递你想要的特性的名称,以及一些可以测试类型T的代码。因此:

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

创建上述特性类。

顺便提一句,上面的技术是微软称之为"表达式sfinae"的一部分,而且他们的2013年编译器非常失败。

注意,在C++ 1Y中,下面的语法是可能的:

1
2
3
4
5
6
7
8
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++特性的内联编译条件分支。这样做可能不值得,因为(代码内联)的好处不值得花费(几乎没有人知道它是如何工作的),但是上述解决方案的存在可能是有意义的。


以下是一些用法片段:*所有这些的胆量都在进一步下降。

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

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

检查成员函数void x()

1
2
3
//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

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

检查成员类x

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

检查是否有会员工会x

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

检查成员枚举x

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

检查任何成员函数x而不考虑签名:

1
2
3
4
5
6
7
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;

1
2
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;

细节和核心:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
    - 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!):

创建成员检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//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>            \
            , Alias_##member<AmbiguitySeed_##member>                        \
        >::value                                                            \
    ;                                                                       \
}

创建成员变量检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//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 {}

创建成员检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
//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 {}

创建u成员u类u检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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 {}

创建成员联合检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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 {}

创建成员枚举检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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 {}

创建成员检查:

1
2
3
4
5
6
7
8
9
10
11
12
//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    \
    ;                                           \
}

创建成员检查:

1
2
3
4
5
6
7
8
//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)


如果该方法恰好在基类中定义,则由LITB提出的标准C++解决方案将无法正常工作。

有关处理此情况的解决方案,请参阅:

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

罗马英语翻译。佩雷佩利萨:http://groups.google.com/group/comp.lang.c++.modarded/tree/browse frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?PLI=1

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

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


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

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

不幸的是,它在GCC(MIWW)3.4.5或数字MARS 842N上不起作用,但是它对所有版本的MSVC(包括VC6)和COMUO C++都起作用。

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

更新-2008年11月7日:

看起来,虽然这个代码在语法上是正确的,但是MSVC和COMUO C++显示的行为不符合标准(感谢Leon Timmermans和LITB指出我在正确的方向)。C++ 03标准如下:

14.6.2 Dependent names [temp.dep]

Paragraph 3

In the definition of a class template
or a member of a class template, if a
base class of the class template
depends on a template-parameter, the
base class scope is not examined
during unqualified name lookup either
at the point of definition of the
class template or member or during an
instantiation of the class template or
member.

因此,当msvc或comeau认为TtoString()成员函数在doToString()中的调用站点执行名称查找时,模板被实例化,这是不正确的(即使这实际上是我在本例中寻找的行为)。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#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;
}


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

检查继承的成员函数

下面是该解决方案的一些示例:

实例1:

我们正在检查是否有以下签名的成员:T::const_iterator begin() const

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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::value是错误的,不会导致编译时错误。)

例2

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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不必是默认可构造的,也不必满足任何特殊的概念。该技术也适用于模板成员。

我热切地等待关于这一点的意见。


msvc有"如果存在"和"如果不存在"关键字(doc)。结合尼古拉的sfinae方法类型,我可以创建一个检查gcc和msvc,就像OP所寻找的那样。

更新:可以在此处找到源


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

以下宏可用于检查特定类是否具有特定的typedef。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * @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); \
   }

以下宏可用于检查特定类是否具有特定的成员函数以及给定数量的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * @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的检查,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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::value << std::endl;
  std::cout <<"Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout <<"Hello Function B:" << hello_check::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::value << std::endl;
  std::cout <<"Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout <<"Check Typedef B:" << has_typedef_check::value << std::endl;
}


使用sfinae和模板部分专用化的示例,通过编写Has_foo概念检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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::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");

可用于检查类型是否支持某些"功能"的通用模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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;
};

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

1
2
3
4
5
// 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*>())));

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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


奇怪的是,没有人建议我在这个网站上看到过以下好的技巧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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上工作,但不确定它是否是标准的。


这里有很多答案,但是我失败了,找到了一个执行实际方法解析排序的版本,而没有使用任何新的C++特性(只使用C++ 98个特性)。注意:此版本通过VC++2013、G++5.2.0和联机编译器进行测试和工作。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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++编译器上运行实时演示时,请注意允许数组大小为0,这意味着使用的静态断言不会触发编译器错误,即使失败也不会触发。常用的解决方法是将宏中的"typedef"替换为"extern"。


这里是我的版本,它使用任意arity处理所有可能的成员函数重载,包括模板成员函数,可能使用默认参数。在对某个类类型进行成员函数调用时,它区分了3种互斥的情况,其中给定的arg类型有:(1)有效的,或(2)不明确的,或(3)不可行的。示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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){}
};

现在您可以这样使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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编写,但是,您可以很容易地将它(带有小的调整)移植到具有类型扩展(例如GCC)的非C++ 11。您可以用自己的宏替换has-mem宏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#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++ 14中的所有元编程,只需从FIT库中使用EDCOX1 OR 4来编写此程序:

1
2
3
4
5
6
7
8
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);
}

也可以直接从lambda创建函数:

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

但是,如果使用的编译器不支持通用lambda,则必须编写单独的函数对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()
);


这个解决方案怎么样?

1
2
3
4
5
6
7
8
#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 { };


1
2
3
4
5
6
7
8
9
10
11
12
13
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!";
}


下面是一个工作代码的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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* = nullptr将启用接受额外int参数的函数,该参数优先于使用0调用时接受long参数的函数。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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(0) << std::endl; // false
}


我也有类似的问题:

一种模板类,它可以从几个基本类派生,一些基本类有一个特定的成员,而另一些基本类没有。

我用类似于"typeof"(Nicola Bonelli的)的答案来解决这个问题,但是使用decltype,它可以在MSV上正确编译和运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#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;
}