代码之家  ›  专栏  ›  技术社区  ›  FireAphis david.pfx

检查类是否有方法[重复]

  •  1
  • FireAphis david.pfx  · 技术社区  · 14 年前

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

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

    所以,如果 class T toString() 定义好了,就用了;否则,就不用了。我不知道该怎么做的神奇部分是“函数存在”部分。

    0 回复  |  直到 8 年前
        1
  •  308
  •   L. F.    6 年前

    是的,使用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;
    }
    

        2
  •  258
  •   Brad Larson    6 年前

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

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

    现在来解释一下。首先,我用 expression SFINAE 排除 serialize(_imp) decltype 无效(AKA,函数不存在)。

    这个 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 是helper类型,基本上等同于编写 decltype(void(std::declval<T>().stream(a0)), std::true_type{})
    接下来是 struct has_stream : decltype(...) std::true_type std::false_type 去字型 登记入住 test_stream
    最后, std::declval 给你一个你通过的任何类型的“值”,而不需要你知道如何构造它。注意,这只可能在未评估的上下文中实现,例如 去字型 sizeof 还有其他人。


    请注意 不一定需要,因为 尺寸 (以及所有未评估的上下文)得到了增强。只是那样 去字型 尺寸 重载之一的版本:

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

    这个 long 参数仍然存在,原因相同。数组指针用于提供一个上下文,其中 尺寸

        3
  •  158
  •   Johannes Schaub - litb    11 年前

    允许C++ SFINAE 要使用这个(注意,C++ 11的特点是简单,因为它支持扩展sFANE在几乎任意的表达式——下面是与普通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

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

        4
  •  70
  •   Morwenn    5 年前

    C++ 20 requires 表达

    用C++ 20实现概念和各种工具,如 requires expressions 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";
    }
    

    前置C++ 20检测工具包

    N4502 要求 std::is_detected 它可以用来轻松地在其上编写类型或函数检测元函数。以下是您使用它的方法:

    template<typename T>
    using toString_t = decltype( std::declval<T&>().toString() );
    
    template<typename T>
    constexpr bool has_toString = std::is_detected_v<toString_t, T>;
    

    注意上面的例子是未经测试的。检测工具包在标准库中还不可用,但建议包含一个完整的实现,如果您真的需要,可以轻松地复制它。它对C++ 17的特性很好 if constexpr

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

    C++ 14 -升压哈娜

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

    […]Hana提供了 is_valid 可以与C++ 14通用LAMBDAS相结合的功能,以获得对同一事物的更清洁的实现:

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

    这就给我们留下了一个函数对象 has_toString 返回给定表达式在传递给它的参数上是否有效。结果返回为 IntegralConstant 有什么事 可以传递给更高阶的算法,也可以在函数作用域中定义,因此不需要用实现细节污染命名空间作用域。

    增强.TTI

    Boost.TTI ,在Boost1.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

    解释

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

    或者, 具有成员函数字符串 has_member_function_toString<T, std::string>::value 通过 has_member_function_toString<std::string T::* ()>::value

        5
  •  54
  •   FireAphis david.pfx    14 年前

    虽然这个问题已经两年了,但我敢补充我的答案。希望它能澄清之前无可争议的优秀解决方案。我采纳了尼古拉·博内利和约翰内斯·朔布非常有用的答案,并将它们合并成一个解决方案,即IMHO,更具可读性,更清晰,不需要 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));
    };
    

    我用GCC4.1.2查过了。

        6
  •  30
  •   Aaron McDaid    7 年前

    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";
    }
    
        7
  •  29
  •   Peter G.    10 年前

    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;
    }
    
        8
  •  23
  •   Community CDub    8 年前

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

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

    // See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
    template <typename...>
    using void_t = void;
    
    // Primary template handles all types not supporting the operation.
    template <typename, template <typename> class, typename = void_t<>>
    struct detect : std::false_type {};
    
    // Specialization recognizes/validates only types supporting the archetype.
    template <typename T, template <typename> class Op>
    struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
    

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

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

    N4502号 ,显示了一个更详细的探测:

    // Archetypal expression for assignment operation.
    template <typename T>
    using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())
    
    // Trait corresponding to that archetype.
    template <typename T>
    using is_assignable = detect<T, assign_t>;
    

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

    live example . 它与Clang很好,但不幸的是,在5.1之前的GCC版本遵循了对C++ 11标准的不同解释。 无效的 不按预期工作。Yakk已经提供了解决方案:使用以下定义 无效的 ( 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
    
        9
  •  10
  •   Brett Rossier    12 年前

    下面是一些用法片段: *所有这一切的勇气都在更深处

    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;
    

    :

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

    检查成员类 :

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

    :

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

    检查成员枚举 :

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

    检查任何成员函数

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

    创建成员检查:

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

    创建成员变量检查:

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

    创建成员功能检查:

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

    创建成员类检查:

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

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

    创建成员枚举检查:

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

    //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 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
  •  10
  •   Jan Rüegg    8 年前

    这是一个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_string 如此 has_to_string<T>::value true T 有办法 .toString 在此上下文中可以使用0个参数调用的。

    接下来,我将使用标记发送:

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

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

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

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

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

    创建上述特征类。

    另外,上述技术是MS所称的“expression SFINAE”的一部分,2013年的编译器失败得很厉害。

    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++特性的内联编译条件分支。这样做可能不值得,因为代码(内联代码)的好处不值得花费(在没有人理解它是如何工作的)的情况下,但是上面的解决方案的存在可能是有意义的。

        11
  •  8
  •   Community CDub    8 年前

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

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

        12
  •  7
  •   Community CDub    8 年前

    现在这是一个 美好的

    这里有一个替代方案 Nicola Bonelli's solution 不依赖非标准 typeof 接线员。

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


    更新-2008年11月7日:

    Leon Timmermans litb 为我指出正确的方向)。C++ 03标准如下:

    第3段

    在类模板的定义中 类模板的基类 取决于模板参数 未检查基类范围 在定义 类模板或成员,或在 类模板的实例化或 成员。

    所以,当MSVC或Comeau考虑 toString() 成员函数 T 在中的调用站点执行名称查找 doToString() 当模板被实例化时,这是不正确的(尽管这实际上是我在本例中要寻找的行为)。

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


    #include <iostream>
    #include <string>
    
    struct Hello
    {
        std::string toString() {
            return "Hello";
        }
    };
    
    struct Generic {};
    
    
    // the following namespace keeps the toString() method out of
    //  most everything - except the other stuff in this
    //  compilation unit
    
    namespace {
        std::string toString()
        {
            return "toString not defined";
        }
    
        template <typename T>
        class optionalToStringImpl : public T
        {
        public:
            std::string doToString() {
    
                // in theory, the name lookup for this call to 
                //  toString() should find the toString() in 
                //  the base class T if one exists, but if one 
                //  doesn't exist in the base class, it'll 
                //  find the free toString() function in 
                //  the private namespace.
                //
                // This theory works for MSVC (all versions
                //  from VC6 to VC9) and Comeau C++, but
                //  does not work with MinGW 3.4.5 or 
                //  Digital Mars 8.42n
                //
                // I'm honestly not sure what the standard says 
                //  is the correct behavior here - it's sort 
                //  of like ADL (Argument Dependent Lookup - 
                //  also known as Koenig Lookup) but without
                //  arguments (except the implied "this" pointer)
    
                return toString();
            }
        };
    }
    
    template <typename T>
    std::string optionalToString(T & obj)
    {
        // ugly, hacky cast...
        optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);
    
        return temp->doToString();
    }
    
    
    
    int
    main(int argc, char *argv[])
    {
        Hello helloObj;
        Generic genericObj;
    
        std::cout << optionalToString( helloObj) << std::endl;
        std::cout << optionalToString( genericObj) << std::endl;
        return 0;
    }
    
        13
  •  6
  •   Roshan Roshan    16 年前

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

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

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

    罗马人的英译。佩雷佩利萨: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

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

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

        14
  •  6
  •   nob    14 年前

    MSVC中存在α-IFIFE,而α-IFIFNOTY存在关键字( Doc ). 结合Nicola的SFINAE方法,我可以创建一个GCC和MSVC的检查,就像OP所寻找的那样。

    可以找到源 Here

        15
  •  5
  •   Community CDub    8 年前

    我修改了中提供的解决方案 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); \
       }
    

    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;
    }
    
        16
  •  5
  •   Paul Belanger    7 年前

    通过编写一个 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");
    
        17
  •  4
  •   Alexandre C.    15 年前

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

        18
  •  3
  •   anton_rh    9 年前

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

    #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

        19
  •  2
  •   user1095108    12 年前

    这个解决方案怎么样?

    #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 { };
    
        20
  •  2
  •   user3296587    8 年前

    真实的 方法解析排序,但不使用任何较新的c++功能(仅使用c++98功能)。
    注意:这个版本是用vc++2013,g++5.2.0和联机编译器测试和工作的。

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

    template<typename T> T declval(void);
    
    struct fake_void { };
    template<typename T> T &operator,(T &,fake_void);
    template<typename T> T const &operator,(T const &,fake_void);
    template<typename T> T volatile &operator,(T volatile &,fake_void);
    template<typename T> T const volatile &operator,(T const volatile &,fake_void);
    
    struct yes { char v[1]; };
    struct no  { char v[2]; };
    template<bool> struct yes_no:yes{};
    template<> struct yes_no<false>:no{};
    
    template<typename T>
    struct has_awesome_member {
     template<typename U> static yes_no<(sizeof((
       declval<U>().awesome_member(),fake_void()
      ))!=0)> check(int);
     template<typename> static no check(...);
     enum{value=sizeof(check<T>(0)) == sizeof(yes)};
    };
    
    
    struct foo { int awesome_member(void); };
    struct bar { };
    struct foo_void { void awesome_member(void); };
    struct wrong_params { void awesome_member(int); };
    
    static_assert(has_awesome_member<foo>::value,"");
    static_assert(!has_awesome_member<bar>::value,"");
    static_assert(has_awesome_member<foo_void>::value,"");
    static_assert(!has_awesome_member<wrong_params>::value,"");
    

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

    在g++编译器上运行实时演示时,请注意允许数组大小为0,这意味着使用的静态断言不会触发编译器错误,即使它失败。
    常用的解决方法是将宏中的“typedef”替换为“extern”。

        21
  •  1
  •   Hui    11 年前

    下面是我的版本,它以任意的arity处理所有可能的成员函数重载,包括模板成员函数,可能使用默认参数。当使用给定的参数类型(1)有效,或(2)不明确,或(3)不可行,对某个类类型进行成员函数调用时,它区分了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;
    }
    

    #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
  •   Paul Fultz II    8 年前

    您可以跳过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);
    }
    

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

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

    struct withToString
    {
        template<class T>
        auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
        {
            return obj->toString();
        }
    };
    
    struct withoutToString
    {
        template<class T>
        std::string operator()(T*) const
        {
            return "toString not defined";
        }
    };
    
    FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
        withToString(),
        withoutToString()
    );
    
        23
  •  0
  •   tereshkd    7 年前

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

    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

    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
    }
    
        24
  •  0
  •   Yigal Eilam    6 年前

    我也有类似的问题:

    一种模板类,可以从几个基类派生,有些基类有某个成员,有些基类没有。

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

    #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;
    }
    
        25
  •  -1
  •   Abhishek    6 年前
    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!";
    }