是否可以编写模板来检查函数的存在?

时间:2008-11-02 20:10:48

标签: c++ templates template-meta-programming sfinae

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

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

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

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

32 个答案:

答案 0 :(得分:298)

是的,使用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( 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和gcc 4.1 / 4.3测试过它。我不知道它是否可以移植到运行不同编译器的其他平台。

答案 1 :(得分:254)

这个问题很老了,但是对于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);
}

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

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

0参数用于优先os << obj重载,如果两者都可用(文字0的类型为int,那么第一次重载是更好的匹配)。


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

#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)){};

Live example.

接着解释。首先,sfinae_true是帮助器类型,它基本上与写decltype(void(std::declval<T>().stream(a0)), std::true_type{})相同。优点是它更短 接下来,struct has_stream : decltype(...)最终会从std::true_typestd::false_type继承,具体取决于decltype签入test_stream是否失败。
最后,std::declval为您提供了您传递的任何类型的“值”,而无需您知道如何构建它。请注意,这只能在未评估的上下文中使用,例如decltypesizeof和其他。


请注意,decltype不一定需要sizeof(以及所有未评估的上下文)获得该增强功能。只是decltype已经提供了一种类型,因此只是更清洁。以下是其中一个重载的sizeof版本:

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

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

答案 2 :(得分:157)

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

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

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作为成员函数指针类型!)。

答案 3 :(得分:54)

虽然这个问题已经两年了,但我还是敢补充一下。希望它能澄清以前无可争议的优秀解决方案。我采用了Nicola Bonelli和Johannes Schaub的非常有用的答案,并将它们合并为一个解决方案,即恕我直言,更具可读性,更清晰,不需要typeof扩展名:

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

    typedef char Yes;
    typedef long No;

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

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

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

我用gcc 4.1.2检查了它。 这个功劳主要归功于Nicola Bonelli和Johannes Schaub,如果我的回答可以帮助你,请给他们一个投票:)

答案 4 :(得分:51)

C ++ 20 - requires表达式

使用C ++ 20来概念和各种工具,例如requires expressions,它们是检查函数存在的内置方法。使用tehm,您可以按如下方式重写optionalToString函数:

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

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

Pre-C ++ 20 - 检测工具包

N4502提出了一个包含在C ++ 17标准库中的检测功能,它可以以一种优雅的方式解决问题。而且,它刚被接受进入库基础TS v2。它介绍了一些元函数,包括std::is_detected,它可以用来轻松编写类型或函数检测元函数。以下是您可以使用它的方法:

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

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

请注意,上面的示例未经测试。检测工具包尚未在标准库中提供,但该提案包含一个完整的实现,如果您真的需要它可以轻松复制。它适用于C ++ 17功能if constexpr

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

Boost.TTI

另一个有点惯用的工具包来执行这样的检查 - 尽管不那么优雅 - 是Boost.TTI,在Boost 1.54.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,它将检查的类型作为其第一个模板参数。第二个模板参数对应于成员函数的返回类型,以下参数对应于函数参数的类型。如果班级value具有成员函数true,则成员T包含std::string toString()

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

答案 5 :(得分:29)

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

答案 6 :(得分:29)

这是什么类型的特征。不幸的是,它们必须手动定义。在您的情况下,请想象以下内容:

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

答案 7 :(得分:21)

Well, this question has a long list of answers already, but I would like to emphasize the comment from Morwenn: there is a proposal for C++17 that makes it really much simpler. See N4502 for details, but as a self-contained example consider the following.

This part is the constant part, put it in a header.

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

then there is the variable part, where you specify what you are looking for (a type, a member type, a function, a member function etc.). In the case of the OP:

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

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

The following example, taken from N4502, shows a more elaborate probe:

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

Compared to the other implementations described above, this one is fairly simple: a reduced set of tools (void_t and detect) suffices, no need for hairy macros. Besides, it was reported (see N4502) that it is measurably more efficient (compile-time and compiler memory consumption) than previous approaches.

Here is a live example. It works fine with Clang, but unfortunately, GCC versions before 5.1 followed a different interpretation of the C++11 standard which caused void_t to not work as expected. Yakk already provided the work-around: use the following definition of void_t (void_t in parameter list works but not as return type):

#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

答案 8 :(得分:10)

这是针对一般问题的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 {};

特征has_to_stringhas_to_string<T>::value true当且仅当T具有可在此上下文中使用0参数调用的方法.toString时。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>{} ); }

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

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

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

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

MAKE_CODE_TRAIT

以上操作是创建一个宏T。您传递了所需特征的名称,以及一些可以测试类型MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() ) 的代码。因此:

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

创建上述特征类。

顺便说一句,上面的技术是MS称之为“表达SFINAE”的一部分,他们的2013编译器失败了。

请注意,在C ++ 1y中,可以使用以下语法:

{{1}}

这是一个内联编译条件分支,它滥用了许多C ++特性。这样做可能不值得,因为(代码是内联的)的好处不值得花费(接下来没人理解它是如何工作的),但上述解决方案的存在可能是有意义的。

答案 9 :(得分:9)

以下是一些使用情况摘要: *所有这些的胆量更远

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

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

检查成员函数void x()

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

检查成员变量x

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

检查会员班级x

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

检查成员联盟x

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

检查成员枚举x

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

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

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

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)

答案 10 :(得分:6)

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

SFINAE to check for inherited member functions

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

例1:

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

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不必是默认构造或满足任何特殊概念。该技术也适用于模板成员。

我急切地等待有关此事的意见。

答案 11 :(得分:6)

MSVC具有__if_exists和__if_not_exists关键字(Doc)。加上Nicola的类型-SFINAE方法,我可以像OP一样为GCC和MSVC创建一个检查。

更新:可以找到来源Here

答案 12 :(得分:6)

如果方法碰巧在基类中定义,则litb提供的标准C ++解决方案将无法按预期工作。

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

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

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

这是非常聪明的。但是,这个解决方案的一个问题是,如果被测试的类型是不能用作基类的类型(例如原始类型),则会产生编译器错误

在Visual Studio中,我注意到如果使用没有参数的方法,则需要在argments周围插入一对额外的redundant()以在sizeof表达式中演绎()。

答案 13 :(得分:6)

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

以下是Nicola Bonelli's solution的替代方案,不依赖于非标准typeof运算符。

不幸的是,它不适用于GCC(MinGW)3.4.5或Digital Mars 8.42n,但它适用于所有版本的MSVC(包括VC6)和Comeau C ++。

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


更新 - 2008年11月7日:

虽然这段代码在语法上是正确的,但MSVC和Comeau C ++显示的行为并不符合标准(感谢Leon Timmermanslitb指向我正确的方向)。 C ++ 03标准说明如下:

  

14.6.2从属名称[temp.dep]

     

第3段

     

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

因此,当模板被实例化时,MSVC或Comeau认为toString() T成员函数doToString()toString()中的调用站点执行名称查找时,看起来是错误的(尽管这实际上是我在这种情况下寻找的行为)。

GCC和数字火星的行为看起来是正确的 - 在这两种情况下,非成员#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}}

答案 14 :(得分:4)

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

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

/** 
 * @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;
}

答案 15 :(得分:4)

使用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");

答案 16 :(得分:3)

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

#include <type_traits>

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

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

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

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

检查是否存在与签名foo

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

实施例

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

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

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

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

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

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

    return 0;
}

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

答案 17 :(得分:3)

使用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";
}

答案 18 :(得分:3)

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

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上运行,但不确定它是否是标准的。

答案 19 :(得分:2)

这里有很多答案,但我找不到执行真正的方法解析排序的版本,而没有使用任何较新的c ++特性(仅使用c ++ 98)功能)。
注意:此版本已经过测试,可与vc ++ 2013,g ++ 5.2.0和onlline编译器一起使用。

所以我提出了一个只使用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也不会触发编译器错误。
一个常用的解决方法是用'extern'替换宏中的'typedef。

答案 20 :(得分:1)

您可以跳过C ++ 14中的所有元编程,只需使用fit::conditional库中的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"; }
);

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

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()
);

答案 21 :(得分:1)

这是我的版本,它处理具有任意arity的所有可能的成员函数重载,包括模板成员函数,可能带有默认参数。当使用给定的arg类型对成员函数调用某些类类型时,它区分了3个互斥的场景:(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编写的代码,但是,你可以轻松地将它(带有小调整)移植到具有扩展类型的非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

答案 22 :(得分:1)

这个解决方案怎么样?

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

答案 23 :(得分:1)

还有另一种在C ++ 17中实现它的方法(受boost:hana启发)。

写一次并使用多次。它不需要has_something<T>类型特征类。

////////////////////////////////////////////
// is_valid implementation
////////////////////////////////////////////

#include <type_traits>

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

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

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

////////////////////////////////////////////
// Example
////////////////////////////////////////////

#include <iostream>
#include <string>

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

struct Example2 {
    int X;
};

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

int main() {
    static_assert(IS_VALID(Example, Foo), 
                  "Example class must have Foo member");
    static_assert(IS_VALID(Example, Bar()), 
                  "Example class must have Bar() member function");
    static_assert(!IS_VALID(Example, ZFoo), 
                  "Example class must not have ZFoo member.");
    static_assert(!IS_VALID(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";
}

答案 24 :(得分:0)

以下是工作代码的示例。

foreach ( $sender in $senders ) {
  Get-TransportServer |
    Get-MessageTrackingLog -Start (Get-Date).AddHours(-4) -ResultSize Unlimited -Sender $sender.PrimarySmtpAddress |
      Where-Object { ($_.Recipients -notlike "*@domain.us") -and
      ($_.RecipientCount -eq 1) -and
      ($_.RecipientStatus -notlike "*,*") -and
      ($_.eventid -eq 'RECEIVE') } | ForEach-Object {
        $results += New-Object PSObject -Property @{
        Sender = $_.Sender
        Receiver = $_.Recipients
        MessageSubject = $_.MessageSubject
        RecipientCount = $_.RecipientCount
        TimeStamp = $_.TimeStamp
      }
    }


    }
$results| ConvertTo-Html -Head $style| Out-File $reportpath

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调用时需long

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

true

答案 25 :(得分:0)

我有一个类似的问题:

可以从几个基本类派生的模板类,一些基类具有某个成员,而另一些基类则没有。

我以类似于“ 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;
}

答案 26 :(得分: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!";
}

答案 27 :(得分:0)

我的观点:普遍确定某项是否可调用而不用为每个特征都添加冗长的类型特征,或者使用实验性功能或长代码:

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

答案 28 :(得分:0)

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

根据我后续问题中的answeranswer,这是一个更完整的答案。请注意,这仅适用于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,将使用其他声明。

答案 29 :(得分:0)

可能不如其他示例好,但这是我为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');
}

答案 30 :(得分:0)

我一直在寻找一种方法,它允许以某种方式不将结构名称 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;
    
}

答案 31 :(得分:0)

这是我在 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