如何检查模板化类是否具有成员函数?

How can you check whether a templated class has a member function?

提问人:andy 提问时间:11/3/2008 最后编辑:Jan Schultkeandy 更新时间:9/26/2023 访问量:233465

问:

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

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

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

因此,如果已定义,则使用它;否则,它不会。我不知道该怎么做的神奇部分是“FUNCTION_EXISTS”部分。class TtoString()

C++ 模板 template-元编程 sfinae

评论

6赞 Alice Purcell 9/2/2010
当然,不言而喻,下面的模板答案仅适用于编译时信息,即 T 必须具有 toString。如果传入的 T 子类定义了 toString,但 T 没有定义,则系统会告知 toString 未定义
0赞 iammilind 3/18/2016
可能的重复 如何检查类中是否存在成员名称(变量或函数),无论是否指定类型?,因为它涵盖了从C++03到C++1y的更广泛的问题。
0赞 etham 12/3/2021
请注意,C++20 现在允许使用概念来执行此操作。请参阅 stackoverflow.com/questions/58394556/...en.cppreference.com/w/cpp/language/constraints

答:

30赞 Konrad Rudolph 11/3/2008 #1

这就是类型特征的用途。不幸的是,它们必须手动定义。在你的例子中,想象一下以下内容:

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

评论

6赞 Özgür 3/20/2009
你应该更喜欢 enum 作为特征而不是静态常量:“ 静态常量成员是左值,这迫使编译器实例化和分配静态成员的定义。因此,计算不再局限于纯粹的“编译时”效果。
5赞 Özgür 3/20/2009
“枚举值不是左值(也就是说,它们没有地址)。因此,当您“通过引用”传递它们时,不会使用静态内存。这几乎就像您将计算值作为文本传递一样。这些注意事项促使我们使用枚举值“ C++ 模板:完整指南
26赞 Konrad Rudolph 3/23/2009
Comptrol:不,引用的段落在这里不适用,因为整数类型的静态常量是一个特例!它们的行为与这里的枚举完全相同,并且是首选方式。旧的枚举黑客只有在不遵循 C++ 标准的编译器上才是必需的。
3赞 Konrad Rudolph 5/10/2010
@Roger Pate:不完全是。这里的“在程序中使用”显然是“引用”的同义词。这段话的主流解读,以及所有现代C++编译器实现的解读,是你可以获取静态常量的,而不需要声明它(前一句是这样说的:“......成员可以出现在整数常量表达式中......“)。只有在获取其地址时(通过将其绑定到引用显式或隐式地)时,才需要定义它。&T::x
380赞 Nicola Bonelli 11/3/2008 #2

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

#include <iostream>

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

struct Generic {};    

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

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

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

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

评论

21赞 user23167 11/3/2008
虽然,我对“一”和“二”使用了以下内容:typedef char Small;类 Big{char dummy[2];} 来确保平台因变量大小没有歧义。
7赞 Nicola Bonelli 11/3/2008
我怀疑它存在于地球上一个 sizeof(char) == sizeof(long) 的平台
18赞 Leon Timmermans 11/3/2008
我不完全确定,但我不认为这是便携式的。typeof 是一个 GCC 扩展,这在其他编译器上不起作用。
58赞 MSalters 2/2/2009
不需要 typeof - char[sizeof(&C::helloworld)] 也可以。为了避免 sizeof(long)==sizeof(char),请使用 struct { char[2] };。它的大小必须为 >=2
58赞 hrr 6/11/2011
微不足道,但我花了一段时间才弄清楚:使用 C++0x 时替换为,例如,通过 -std=c++0xtypeofdecltype
7赞 Michael Burr 11/4/2008 #3

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

这是 Nicola Bonelli 解决方案的替代方案,它不依赖于非标准运算符。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()TdoToString()

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

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


#include <iostream>
#include <string>

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

struct Generic {};


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

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

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

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

            return toString();
        }
    };
}

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

    return temp->doToString();
}



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

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

评论

1赞 Leon Timmermans 11/5/2008
不,它不符合标准,尽管我认为如果您打开 -fpermissive 选项,它将在 GCC 中工作。
0赞 Michael Burr 11/5/2008
我知道这些评论没有给太多空间,但您能指出为什么它不符合标准的信息吗?(我不是在争论——我很好奇)
0赞 Johannes Schaub - litb 11/7/2008
Mike B:该标准在 3.10 p15 中说:“如果程序试图通过以下类型之一以外的左值访问对象的存储值,则行为是未定义的”,并且该列表确实不包括您这样做的情况。
4赞 Johannes Schaub - litb 11/7/2008
我不确定为什么它没有添加我的另一条评论:您的 toString 调用是不合格的。因此,它将始终调用 free 函数,而永远不会调用基函数,因为基类依赖于模板类型参数。
1赞 Johannes Schaub - litb 11/16/2008
等等,我从标准中得到了关于这一点的明确引用: 9.3.1/1:“如果为非 X 类型或从 X 派生的类型的对象调用类 X 的非静态成员函数,则行为是未定义的。这只是运气,有人引用了它,并告诉我他从哪里得到:)
173赞 Johannes Schaub - litb 11/5/2008 #4

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

但请注意,您不能只在该分支中调用该函数。由于编译器将检查两个分支的有效性,因此在函数不存在的情况下将失败。一种方法是再次使用 SFINAE(也可以从 boost 获得):toStringifenable_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.";
}

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

评论

7赞 j_random_hacker 11/17/2010
我喜欢如何确保签名完全一致。有没有办法使它能够匹配任何可以以可以调用具有签名的方法的方式调用的方法?(例如,如果 = ,允许匹配。type_checkSignSignstd::string(T::*)()std::string T::toString(int default = 42, ...)
5赞 SCFrench 1/24/2011
我只是弄清楚了一些对我来说并不明显的事情,所以如果它对其他人有帮助:chk 不是也不需要定义!sizeof 运算符确定 chk 输出的大小,而无需调用 chk。
3赞 Jan Hudec 5/22/2012
@deek0146:是的,不能是基元类型,因为指向方法的 T 声明的指针不受 SFINAE 的约束,并且对于任何非类 T 都会出错。 IMO 最简单的解决方案是与 boost 的检查相结合。Tis_class
2赞 Frank 8/20/2012
如果我是模板化函数,我该如何使它工作?toString
4赞 Dan Nissenbaum 3/29/2013
这是 Boost 中的(或任何等效的东西)吗?
7赞 Roshan 2/2/2009 #5

如果该方法恰好在基类中定义,则 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 中,我注意到如果使用没有参数的方法,则需要在参数周围插入一对额外的冗余 ( ) 以在 sizeof 表达式中推导出 ( )。

评论

0赞 Johannes Schaub - litb 7/9/2009
嗯,使用该帖子的想法开发了自己的版本,我发现这个想法还有其他一些缺点,所以我再次从我的答案中删除了代码。一是所有函数在目标类型中都必须是公共的。因此,您无法检查此处的“f”函数:因为其中一个函数是私有的(这是因为代码确实如此,如果无法访问任何函数,则会失败)。struct g { void f(); private: void f(int); };using g::f;f
4赞 Alexandre C. 6/23/2010 #6

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

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

60赞 FireAphis 9/2/2010 #7

虽然这个问题已经有两年了,但我敢于添加我的答案。希望它能澄清以前的、无可争议的优秀解决方案。我接受了 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,所以如果我的回答对您有所帮助,请给他们投一票:)

评论

1赞 Alastair Irvine 12/12/2013
只是想知道,这是否做了康拉德·鲁道夫(Konrad Rudolph)下面的解决方案所没有的任何事情?
3赞 FireAphis 12/15/2013
@AlastairIrvine,这个解决方案隐藏了里面的所有逻辑,Konrad 给用户带来了一些负担。虽然简短且可读性更强,但 Konrad 的解决方案要求每个类都有一个单独的模板专用化,该类具有 .如果你编写了一个通用库,希望与任何类一起工作(想想像boost这样的东西),那么要求用户定义一些晦涩的模板的其他专业化可能是不可接受的。有时,最好编写非常复杂的代码,以保持公共接口尽可能简单。toString
0赞 Pablo Halpern 7/16/2022
您应该解释在类模板中嵌套函数指针类型的原因。IIUC,这是为了避免在不是类时出现实例化错误,对吧?ToStringType
6赞 nob 1/27/2011 #8

MSVC 具有 __if_exists 和 __if_not_exists 关键字 (Doc)。结合 Nicola 的 typeof-SFINAE 方法,我可以像 OP 所寻找的那样为 GCC 和 MSVC 创建检查。

更新:来源可以在这里找到

10赞 Brett Rossier 6/13/2011 #9

以下是一些用法片段: *所有这一切的胆量都在更深处

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

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

检查成员函数 void x():

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

检查成员变量 x

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

检查成员类 x

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

检查成员工会 x

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

检查成员枚举 x

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

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

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

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

细节和核心:

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

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

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

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

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

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

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

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

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

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

评论

1赞 v.oddou 3/6/2015
您知道为什么如果我们更改为免费函数检查:它无法使用“未声明的标识符”来构建,并提及我们要检查的函数的名称?因为我希望 SFINAE 不会让它成为一个错误,它对成员就是这样做的,为什么不为免费功能呢?sig_check<func_sig, &T::func_name>sig_check<func_sig, &func_name>
0赞 Brett Rossier 3/7/2015
我认为这与自由函数不是类或结构这一事实有关。这种推断成员存在的技术实际上以 C++ 中的多重继承机制为中心,在仅用于托管要检查的成员的存根类与实际检查成员的类之间强制存在歧义。不过,这是一个有趣的问题,没有想过。您可以查看其他 C++11/14 成员检查技术,我已经在新标准中看到了一些聪明的东西。
0赞 v.oddou 3/9/2015
感谢您的回答,我想我可能需要更深入地检查您提供的有关继承的情报,因为直到现在我还没有看到简单地依靠 SFINAE 来制作一个表达式(在模板类型参数中表达对成员的访问)与多重继承之间有任何关联。但我完全相信,在C++中,即使是遥远的概念也会相互影响。现在对于免费函数,这个问题很有趣:stackoverflow.com/questions/26744589 T.C 答案似乎使用了声明虚拟人的技巧来避免“未声明的标识符”
8赞 kispaljr 1/6/2012 #10

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

SFINAE 检查继承的成员函数

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

示例 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

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

我正在热切地等待有关此事的意见。

302赞 Xeo 2/6/2012 #11

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

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

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

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

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

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

如果两者都可用,则该参数用于首选重载(文本是类型,因此第一个重载是更好的匹配)。0os << obj0int


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

#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_truedecltype(void(std::declval<T>().stream(a0)), std::true_type{})struct has_stream : decltype(...)std::true_typestd::false_typedecltypetest_streamstd::declvaldecltypesizeof


请注意,这不一定是必需的,因为(以及所有未评估的上下文)都得到了增强。只是已经提供了一种类型,因此更干净。下面是其中一个重载的版本:decltypesizeofdecltypesizeof

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

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

评论

4赞 Johannes Schaub - litb 4/12/2014
over 的优点还在于,函数调用的特制规则不会引入临时函数(因此,您不必对返回类型的析构函数具有访问权限,并且如果返回类型是类模板实例化,则不会导致隐式实例化)。decltypesizeof
6赞 Jonathan 1/26/2015
Microsoft 尚未在其 C++ 编译器中实现 Expression SFINAE。只是想我可能会帮助节省一些人的时间,因为我很困惑为什么这对我不起作用。不过不错的解决方案,迫不及待地想在 Visual Studio 中使用它!
1赞 Gabriel 10/4/2015
不得不说,这将编译而不是断言,因为 char 可转换为 int,所以如果不需要这种行为并希望所有参数类型都匹配我不知道如何实现?static_assert(has_stream<X, char>() == true, "fail X");
5赞 André 2/17/2017
如果你和我一样对 decltype 的两个论点感到困惑:decltype 真的只需要一个;逗号在这里是一个运算符。查看 stackoverflow.com/questions/16044514/...
1赞 John 5/23/2017
这在需要完整类型的情况下非常有效,但在不需要的情况下,这将为不完整(正向声明)类型提供误报。我添加了一个对应项,并在重写上使用了检测析构函数存在的返回类型。这排除了仍然不完整或没有公共析构函数的类型。排除非公共析构函数对我来说是可以接受的。sfinae_falselong
0赞 iammilind 5/11/2013 #12

C++03 方式

#define HasMember(NAME) \
  template<class Class, typename Type = void> \
  struct HasMember_##NAME \
  { \
    typedef char (&yes)[2]; \
    template<unsigned long> struct exists; \
    template<typename V> static yes Check (exists<sizeof(static_cast<Type>(&V::NAME))>*); \
    template<typename> static char Check (...); \
    static const bool value = (sizeof(Check<Class>(0)) == sizeof(yes)); \
  }; \
  template<class Class> \
  struct HasMember_##NAME<Class, void> \
  { \
    typedef char (&yes)[2]; \
    template<unsigned long> struct exists; \
    template<typename V> static yes Check (exists<sizeof(&V::NAME)>*); \
    template<typename> static char Check (...); \
    static const bool value = (sizeof(Check<Class>(0)) == sizeof(yes)); \
  }

使用上面的宏,您可以找到类中任何成员的存在,无论是变量还是方法。如果有 2 个同名方法,那么我们还必须提供方法的类型。

用法

#include<iostream>
struct S
{
  void Foo () const {}
//  void Foo () {}  // If uncommented then, SFINAE fails      
  int i;
};

HasMember(Foo);
HasMember(i);

int main ()
{
  std::cout << HasMember_Foo<S, void (S::*) () const>::value << "\n";
  std::cout << HasMember_Foo<S>::value << "\n";
  std::cout << HasMember_i<S, int (S::*)>::value << "\n";
  std::cout << HasMember_i<S>::value << "\n";
}

如果 中存在 2 个同名方法,则第 2 个可以打印 0。如果是成员变量,类型(如第 3 个中所述)是多余的,因为变量名称只能为 1。但是,要检查特定类型,它很有用(对于方法和变量)。coutFooScout

评论

0赞 iammilind 3/18/2016
现在在这里以更有条理的方式回答了这个问题。
4赞 user1095108 7/9/2013 #13

这个解决方案怎么样?

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

评论

0赞 Yakk - Adam Nevraumont 6/2/2014
如果重载,则失败,因为不明确。toString&U::toString
0赞 user1095108 6/3/2014
@Yakk我认为演员可以解决这个问题。
5赞 Shubham 11/6/2013 #14

我修改了 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); \
   }

我们可以使用上面的 2 个宏来执行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;
}

评论

0赞 ACyclic 9/2/2016
您可以对此进行改进,以支持具有模板参数的成员函数。将模板 <typename T> 更改为模板 <typename T, typename ...Args>,那么您可以在宏省略号中使用“Args...”来创建一个带有可变参数模板参数的检查结构。 例如。检测“void onNext(const T &)”方法...HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... );template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
1赞 Hui 1/11/2014 #15

这是我的版本,它以任意性处理所有可能的成员函数重载,包括模板成员函数,可能使用默认参数。当使用给定的参数类型对某个类类型进行成员函数调用时,它区分了 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 编写的代码,但是,您可以轻松地将其移植(稍作调整)到具有扩展类型(例如 gcc)的非 c++11 中。您可以将 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

205赞 Morwenn 2/25/2014 #16

C++20 - 表达式requires

C++20 带来了概念和各种工具,例如 requires 表达式,它们是检查函数是否存在的内置方法。使用它们,您可以按如下方式重写函数: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,可用于在其顶部轻松编写类型或函数检测元函数。以下是如何使用它:requires

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

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

请注意,上面的示例是未经测试的。检测工具包在标准库中尚不可用,但该提案包含一个完整的实现,如果您确实需要它,可以轻松复制它。它与 C++17 功能配合得很好:if constexpr

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

C++14 - 提升.哈纳

Boost.Hana 显然构建在这个特定示例的基础上,并在其文档中提供了 C++14 的解决方案,所以我将直接引用它:

[...]Hana 提供了一个可以与 C++14 通用 lambda 结合使用的函数,以获得相同事物的更简洁的实现:is_valid

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

这给我们留下了一个函数对象,该对象返回给定表达式在我们传递给它的参数上是否有效。结果以 返回 的形式返回,因此 constexpr-ness 在这里不是问题,因为函数的结果无论如何都表示为类型。现在,除了不那么冗长(这是一句话!)之外,意图也更加清晰。其他好处是可以传递给高阶算法,也可以在函数范围内定义,因此无需用实现细节污染命名空间范围。has_toStringIntegralConstanthas_toString

Boost.TTI的

另一个执行此类检查的惯用工具包 - 尽管不那么优雅 - 是 Boost 1.54.0 中引入的 Boost.TTI。对于您的示例,您必须使用宏 .以下是如何使用它: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;

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

解释

宏生成元函数,该元函数将选中的类型作为其第一个模板参数。第二个模板参数对应成员函数的返回类型,以下参数对应函数参数的类型。如果类具有成员函数,则该成员包含。BOOST_TTI_HAS_MEMBER_FUNCTIONhas_member_function_toStringvaluetrueTstd::string toString()

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

评论

1赞 ZFY 3/13/2020
比03更简洁
0赞 Morwenn 3/13/2020
@ZFY 我认为 Boost.TTI 也适用于 C++03,但它是最不优雅的解决方案。
2赞 Bernd 4/5/2020
C++20 解决方案真的有效吗?我想要它 - 但它被 g++ 和 msvc 拒绝 - 只被 clang 接受。
2赞 Bernd 4/5/2020
在 cppreference 中,您可以阅读: 如果 requires-expression 在其需求中包含无效的类型或表达式,并且它没有出现在模板化实体的声明中,则程序的格式不正确。
0赞 Morwenn 4/5/2020
@BerndBaumanns真的吗?我让它与 GCC trunk 一起使用:godbolt.org/z/CBwZdE 也许你是对的,我只检查它是否有效,但没有根据标准措辞检查它是否合法。
14赞 Yakk - Adam Nevraumont 6/2/2014 #17

这是一个 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 {};

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

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

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

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

上面所做的是创建一个 宏 .你向它传递你想要的特征的名称,以及一些可以测试类型的代码。因此:MAKE_CODE_TRAITT

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

创建上述 traits 类。

顺便说一句,上述技术是 MS 所谓的“表达式 SFINAE”的一部分,他们的 2013 编译器失败得很厉害。

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

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

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

评论

0赞 tower120 6/23/2014
这处理私人案件吗?
0赞 Yakk - Adam Nevraumont 6/23/2014
@tower120 我必须进行实验:模板如何与私有/公共/受保护的交互对我来说有点晦涩难懂。但是,在哪里调用并不重要。has_to_string
0赞 tower120 6/23/2014
但你知道,如果从另一边看......我们可以从派生类中访问受保护的成员。也许如果把所有这些东西放在类中,并从结构转换为 constexpr 函数......
0赞 tower120 6/23/2014
在这里,看看这个 coliru.stacked-crooked.com/a/ee94d16e7c07e093 我就是不能让它 constexpr
0赞 Yakk - Adam Nevraumont 7/15/2014
@tower120 C++1y 让它工作:coliru.stacked-crooked.com/a/d8cdfff24a171394
35赞 akim 6/15/2015 #18

好吧,这个问题已经有一长串答案了,但我想强调 Morwenn 的评论:有一个针对 C++17 的提案,它确实简单得多。有关详细信息,请参阅 N4502,但作为一个独立的示例,请考虑以下内容。

这部分是常量部分,把它放在一个标题中。

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

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

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

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

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

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

以下示例取自 N4502,显示了一个更复杂的探头:

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

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

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

这是一个活生生的例子。它适用于 Clang,但不幸的是,5.1 之前的 GCC 版本遵循了对 C++11 标准的不同解释,导致无法按预期工作。Yakk 已经提供了解决方法:使用以下定义(参数列表中的void_t有效,但不能作为返回类型):void_tvoid_t

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

评论

0赞 plasmacel 12/4/2016
是否可以扩展它以检测非成员函数?
1赞 akim 12/5/2016
当然。仔细看一下这些示例:你基本上提供了一个表达式,并检查它是否有效。没有任何内容要求此表达式仅与成员函数调用有关。
0赞 tlonuk 3/11/2017
N4502 (open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf) 是未来的发展方向...我一直在寻找一种简洁的方法来检测类型上的东西,而 N4502 就是要走的路。
37赞 Aaron McDaid 8/6/2015 #19

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

评论

5赞 Adrian W 6/14/2018
这简单而优雅,但严格来说并不能回答 OP 的问题:您不会让调用者检查函数的存在,而是始终提供它。但无论如何都很好。
0赞 Aaron McDaid 6/16/2018
@AdrianW,好点子。我已经更新了我的答案。不过我还没有测试过
0赞 aPonza 12/21/2018
如果它对其他人有帮助,如果没有在可变调态过载之前,我就无法完成这项工作:它没有被考虑解决。template<typename>
2赞 Peter 10/11/2019
同样,这是无效的 C++11。
2赞 user3296587 10/25/2015 #20

这里有很多答案,但我没能找到一个版本,它执行真正的方法解析排序,同时不使用任何较新的 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++ 编译器上运行实时演示时,请注意允许数组大小为 0,这意味着使用的static_assert不会触发编译器错误,即使它失败。
常用的解决方法是将宏中的“typedef”替换为“extern”。

评论

0赞 the swine 11/11/2016
静态断言不起作用。您需要使用数组大小 -1 而不是 0(尝试将 )。我在 CRTP 中使用它,我想确定派生类是否具有特定函数——结果证明它不起作用,但您的断言总是通过的。我因为那个而掉了一些头发。static_assert(false);
0赞 user3296587 12/7/2016
我假设你使用的是 g++。请注意,gcc/g++ 有一个扩展,允许零大小的数组 (gcc.gnu.org/onlinedocs/gcc/Zero-Length.html)
0赞 einpoklum 12/13/2018
您能否重写它以免运算符过载?例如,选择其他运营商?另外,避免用has_awesome_member以外的任何东西污染命名空间?
0赞 user3296587 7/27/2019
对不起,但它必须如此,因为这是唯一由内置 void-type 实现的运算符(您可以编写 ,但任何其他运算符(如 in 总是会导致编译器错误并且无法覆盖),这意味着这是能够使用 void 返回类型执行函数所必需的。- 至于命名空间污染:当然,你可以把所有东西(除了,它必须保持作为全局运算符的可见)放入某个命名空间中,并调整为简单地使用该命名空间。operator ,((void)0, foo())((void)0 + foo())operator ,()has_awesome_member
1赞 Paul Fultz II 1/15/2016 #21

您可以跳过 C++ 14 中的所有元编程,只需使用 fit 库中的 fit::conditional 编写以下内容:

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

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

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

评论

2赞 einpoklum 12/13/2018
编写它以不必依赖标准以外的任何库有多容易?fit
4赞 anton_rh 5/10/2016 #22

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

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

检查是否存在与签名兼容的方法的模板foodouble(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

评论

0赞 CJCombrink 7/12/2017
有没有办法将 内联到 的模板调用中。我想说的是:.这样做的原因是,我想为每个不同的函数定义一个签名,在检查函数之前,我要检查该签名?has_foois_supportedstd::cout << is_supported<magic.foo(), struct1>::value << std::endl;has_foo
0赞 tereshkd 11/2/2017 #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<T>* = nullptr将启用采用额外参数的函数,该参数优先于使用 .intlong0

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

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

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


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

    std::cout << toStringExists<A>(0) << std::endl; // true
    std::cout << toStringExists<B>(0) << std::endl; // false
}
7赞 Paul Belanger 1/22/2018 #24

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

评论

0赞 Elliott 10/17/2021
如果我们默认第二个,那么我们可以做(godbolttypenameintHas_foo<T, decltype(std::declval<T>().foo(0, 0))> : std::true_type {};)
11赞 Bernd 4/5/2020 #25

使用 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赞 Dmytro Ovdiienko 6/10/2020 #26

在 C++17 中执行此操作的另一种方法(受 .boost:hana

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

溶液

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

#include <type_traits>

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

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

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

测试

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

#include <iostream>
#include <string>

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

struct Example2 {
    int X;
};

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

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

    Example e1;
    Example2 e2;

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

评论

0赞 prehistoricpenguin 6/24/2021
是否可以检测私人成员?
1赞 Dmytro Ovdiienko 6/24/2021
@prehistoricpenguin 这取决于你在哪里使用宏。如果你在类中使用它来检查私有成员的存在,那么是的,它确实有效。如果您在类外使用该宏,例如在某些函数中,则宏不起作用。但是,如果您将此函数添加到列表中,那么它就可以工作。has_memberfriend
1赞 Anton Dyachenko 2/25/2023
我从这个问题的答案中尝试的最佳选择是:最少的代码量,不仅适用于成员,也适用于免费函数。我编写了通用的 toString,它检查了我项目中将值转换为字符串的所有可能方法。最佳答案。
3赞 Sean 8/28/2020 #27

我的看法是:普遍确定某些东西是否可调用,而无需为每个特征创建冗长的类型特征,或者使用实验性特征或长代码:

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);
5赞 TommyD 9/10/2020 #28

我知道这个问题已经有好几年了,但我认为对于像我这样的人来说,有一个更完整的更新答案会很有用,这个答案也适用于重载的方法,例如 .conststd::vector<>::begin

根据这个答案和我后续问题的答案,这里有一个更完整的答案。请注意,这仅适用于 C++11 及更高版本。

#include <iostream>
#include <vector>

class EmptyClass{};

template <typename T>
class has_begin
{
    private:
    has_begin() = delete;
    
    struct one { char x[1]; };
    struct two { char x[2]; };

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

public:
    static constexpr bool value = sizeof(test<T>(0)) == sizeof(one);
};
    
int main(int argc, char *argv[])
{
    std::cout << std::boolalpha;
    std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
    std::cout << "EmptyClass::begin() exists: " << has_begin<EmptyClass>::value << std::endl;
    return 0;
}

或者较短的版本:

#include <iostream>
#include <vector>

class EmptyClass{};

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

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

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

请注意,此处必须提供完整的示例调用。这意味着,如果我们测试该方法的存在,那么我们将把 .resizeresize(0)

深奥魔法解释

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

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

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

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

评论

1赞 Elliott 10/17/2021
我试图理解为什么我们需要这个零件。我本来以为这会起作用:...... (避免)。知道为什么没有吗?void(...)template <auto> using v_to_void = void;v_to_void<std::declval<T&>().begin()>decltype
1赞 Thomas Eding 11/22/2020 #29

可能不如其他示例好,但这就是我为 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');
}
16赞 Jean-Michaël Celerier 6/12/2021 #30

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

评论

1赞 alexpanter 11/10/2023
语言越来越好!我更喜欢这个,而不是任何 SFINAE 的混乱或复杂的宏观扩展。