Is it possible to write a template to check for a function's existence?
是否可以编写一个更改行为的模板,这取决于是否在类上定义了某个成员函数?
下面是我想写的一个简单例子:
1 2 3 4 5 6 7 8 | template<class T> std::string optionalToString(T* obj) { if (FUNCTION_EXISTS(T->toString)) return obj->toString(); else return"toString not defined"; } |
所以,如果
是的,使用sfinae,您可以检查给定的类是否提供了某种方法。工作代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #include <iostream> struct Hello { int helloworld() { return 0; } }; struct Generic {}; // SFINAE test template <typename T> class has_helloworld { typedef char one; typedef long two; template <typename C> static one test( typeof(&C::helloworld) ) ; template <typename C> static two test(...); public: enum { value = sizeof(test<T>(0)) == sizeof(char) }; }; int main(int argc, char *argv[]) { std::cout << has_helloworld<Hello>::value << std::endl; std::cout << has_helloworld<Generic>::value << std::endl; return 0; } |
我刚刚用Linux和GCC4.1/4.3测试过它。我不知道它是否可以移植到运行不同编译器的其他平台上。
这个问题是旧的,但是用C++ 11,我们得到了一个新的方法来检查函数的存在(或者任何非类型成员的存在,真的),依赖于sFANAE:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | template<class T> auto serialize_imp(std::ostream& os, T const& obj, int) -> decltype(os << obj, void()) { os << obj; } template<class T> auto serialize_imp(std::ostream& os, T const& obj, long) -> decltype(obj.stream(os), void()) { obj.stream(os); } template<class T> auto serialize(std::ostream& os, T const& obj) -> decltype(serialize_imp(os, obj, 0), void()) { serialize_imp(os, obj, 0); } |
现在来解释一下。首先,如果
如果两个参数都可用,则使用
现在,您可能需要一个特性来检查函数是否存在。幸运的是,写起来很容易。不过,请注意,您需要为可能需要的每个不同的函数名自己编写一个特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <type_traits> template<class> struct sfinae_true : std::true_type{}; namespace detail{ template<class T, class A0> static auto test_stream(int) -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>; template<class, class A0> static auto test_stream(long) -> std::false_type; } // detail:: template<class T, class Arg> struct has_stream : decltype(detail::test_stream<T, Arg>(0)){}; |
活生生的例子。
以及解释。首先,
注意,不一定需要
1 2 3 4 5 6 | template<class T> void serialize_imp(std::ostream& os, T const& obj, int, int(*)[sizeof((os << obj),0)] = 0) { os << obj; } |
由于同样的原因,
C++允许SfENA用于此(注意C++ 11的特点,这是简单的,因为它支持几乎任意表达式的扩展sFANE——下面是用普通C++ 03编译器编写的):
1 2 3 4 5 6 7 8 9 10 | #define HAS_MEM_FUNC(func, name) \ template<typename T, typename Sign> \ struct name { \ typedef char yes[1]; \ typedef char no [2]; \ template <typename U, U> struct type_check; \ template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \ template <typename > static no &chk(...); \ static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \ } |
上面的模板和宏试图实例化一个模板,给它一个成员函数指针类型和实际的成员函数指针。如果类型不合适,sfinae会导致忽略模板。用法如下:
1 2 3 4 5 6 7 8 9 10 | HAS_MEM_FUNC(toString, has_to_string); template<typename T> void doSomething() { if(has_to_string<T, std::string(T::*)()>::value) { ... } else { ... } } |
但请注意,您不能在那个if分支中仅仅调用该
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | template<bool C, typename T = void> struct enable_if { typedef T type; }; template<typename T> struct enable_if<false, T> { }; HAS_MEM_FUNC(toString, has_to_string); template<typename T> typename enable_if<has_to_string<T, std::string(T::*)()>::value, std::string>::type doSomething(T * t) { /* something when T has toString ... */ return t->toString(); } template<typename T> typename enable_if<!has_to_string<T, std::string(T::*)()>::value, std::string>::type doSomething(T * t) { /* something when T doesnt have toString ... */ return"T::toString() does not exist."; } |
玩得开心。它的优点是它也适用于重载的成员函数,也适用于常量成员函数(请记住使用
尽管这个问题已经两年了,我还是敢补充我的答案。希望它能澄清之前无可争议的优秀解决方案。我从尼古拉·博内利和约翰内斯·肖布那里得到了非常有用的答案,并将它们合并成一个解决方案,即,imho,更易读、更清晰,不需要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | template <class Type> class TypeHasToString { // This type won't compile if the second template parameter isn't of type T, // so I can put a function pointer type in the first parameter and the function // itself in the second thus checking that the function has a specific signature. template <typename T, T> struct TypeCheck; typedef char Yes; typedef long No; // A helper struct to hold the declaration of the function pointer. // Change it if the function signature changes. template <typename T> struct ToString { typedef void (T::*fptr)(); }; template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*); template <typename T> static No HasToString(...); public: static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes)); }; |
我和GCC 4.1.2核对过。这主要归功于尼古拉·博内利和约翰内斯·朔布,所以如果我的回答对你有帮助的话,就给他们投票吧:)
C++ 20—EDOCX1·0表达式
用C++ 20来生成概念和各种工具,如EDCOX1、0、表达式,这些表达式是检查函数存在的一种内置方法。有了tehm,您可以重写
1 2 3 4 5 6 7 8 9 10 11 12 | template<class T> std::string optionalToString(T* obj) { constexpr bool has_toString = requires(const T& t) { t.toString(); }; if constexpr (has_toString) return obj->toString(); else return"toString not defined"; } |
Pre-C++20-检测工具包
N4502提出了一种包含在C++ 17标准库中的检测方法,可以以一种稍微优雅的方式解决问题。此外,它刚刚被图书馆基础TS v2接受。它引入了一些元函数,包括
1 2 3 4 5 | template<typename T> using toString_t = decltype( std::declval<T&>().toString() ); template<typename T> constexpr bool has_toString = std::is_detected_v<toString_t, T>; |
请注意,上面的示例未经测试。标准库中还没有检测工具包,但是该建议包含一个完整的实现,如果您确实需要的话,可以很容易地复制它。它对C++ 17的特性EDOCX1,4表示很好:
1 2 3 4 5 6 7 8 | template<class T> std::string optionalToString(T* obj) { if constexpr (has_toString<T>) return obj->toString(); else return"toString not defined"; } |
助听器
另一个执行这种检查的惯用工具包是boost.tti,在boost 1.54.0中引入。对于您的示例,您必须使用宏
1 2 3 4 5 6 7 8 | #include <boost/tti/has_member_function.hpp> // Generate the metafunction BOOST_TTI_HAS_MEMBER_FUNCTION(toString) // Check whether T has a member function toString // which takes no parameter and returns a std::string constexpr bool foo = has_member_function_toString<T, std::string>::value; |
然后,可以使用
解释
宏
或者,
这就是它们的类型特征。不幸的是,它们必须手动定义。在您的案例中,请想象以下情况:
1 2 3 4 5 6 7 8 9 | template <typename T> struct response_trait { static bool const has_tostring = false; }; template <> struct response_trait<your_type_with_tostring> { static bool const has_tostring = true; } |
C++ 11的简单解决方案:
1 2 3 4 5 6 7 8 9 10 | template<class T> auto optionalToString(T* obj) -> decltype( obj->toString() ) { return obj->toString(); } auto optionalToString(...) -> string { return"toString not defined"; } |
更新,3年后:(这是未测试的)。为了检验是否存在,我认为这是可行的:
1 2 3 4 5 6 7 8 9 10 | template<class T> constexpr auto test_has_toString_method(T* obj) -> decltype( obj->toString() , std::true_type{} ) { return obj->toString(); } constexpr auto test_has_toString_method(...) -> std::false_type { return"toString not defined"; } |
这个问题已经有很长的答案了,但是我想强调一下MMOWN的评论:有一个C++ 17的建议,使它更简单。有关详细信息,请参见N4502,但作为一个独立的示例,请考虑以下内容。
这部分是常量部分,放在标题中。
1 2 3 4 5 6 7 8 9 10 11 | // See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf. template <typename...> using void_t = void; // Primary template handles all types not supporting the operation. template <typename, template <typename> class, typename = void_t<>> struct detect : std::false_type {}; // Specialization recognizes/validates only types supporting the archetype. template <typename T, template <typename> class Op> struct detect<T, Op, void_t<Op<T>>> : std::true_type {}; |
然后是变量部分,指定要查找的内容(类型、成员类型、函数、成员函数等)。对于OP:
1 2 3 4 5 | template <typename T> using toString_t = decltype(std::declval<T>().toString()); template <typename T> using has_toString = detect<T, toString_t>; |
下面的例子取自N4502,展示了一个更复杂的探针:
1 2 3 4 5 6 7 | // Archetypal expression for assignment operation. template <typename T> using assign_t = decltype(std::declval<T&>() = std::declval<T const &>()) // Trait corresponding to that archetype. template <typename T> using is_assignable = detect<T, assign_t>; |
与上面描述的其他实现相比,这个实现相当简单:一组简化的工具(
这是一个活生生的例子。它与Clang工作良好,但不幸的是,在5.1之前的GCC版本遵循了对C++ 11标准的不同解释,这导致EDCOX1 0不按预期工作。Yakk已经提供了解决方案:使用
1 2 3 4 5 6 7 8 9 10 11 12 13 | #if __GNUC__ < 5 && ! defined __clang__ // https://stackoverflow.com/a/28967049/1353549 template <typename...> struct voider { using type = void; }; template <typename...Ts> using void_t = typename voider<Ts...>::type; #else template <typename...> using void_t = void; #endif |
这是一个C++ 11解决一般问题的方法,"如果我做了X,它会编译吗?"
1 2 3 4 5 6 7 | template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void` template<class T> using type_sink_t = typename type_sink<T>::type; template<class T, class=void> struct has_to_string : std::false_type {}; \ template<class T> struct has_to_string< T, type_sink_t< decltype( std::declval<T>().toString() ) > >: std::true_type {}; |
特征
接下来,我将使用标签调度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | namespace details { template<class T> std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) { return obj->toString(); } template<class T> std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) { return"toString not defined"; } } template<class T> std::string optionalToString(T* obj) { return details::optionalToString_helper( obj, has_to_string<T>{} ); } |
它比复杂的sfinae表达式更易于维护。
如果你发现自己做得太多,你可以用宏来写这些特征,但是它们相对简单(每行几行),所以可能不值得:
1 2 3 | #define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \ template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \ template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {}; |
上面所做的就是创建一个宏
1 | MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() ) |
创建上述特性类。
顺便提一句,上面的技术是微软称之为"表达式sfinae"的一部分,而且他们的2013年编译器非常失败。
注意,在C++ 1Y中,下面的语法是可能的:
1 2 3 4 5 6 7 8 | template<class T> std::string optionalToString(T* obj) { return compiled_if< has_to_string >(*obj, [&](auto&& obj) { return obj.toString(); }) *compiled_else ([&]{ return"toString not defined"; }); } |
这是一个滥用大量C++特性的内联编译条件分支。这样做可能不值得,因为(代码内联)的好处不值得花费(几乎没有人知道它是如何工作的),但是上述解决方案的存在可能是有意义的。
以下是一些用法片段:*所有这些的胆量都在进一步下降。
检查给定类中的成员
1 2 | CREATE_MEMBER_CHECK(x); bool has_x = has_member_x<class_to_check_for_x>::value; |
检查成员函数
1 2 3 | //Func signature MUST have T as template variable here... simpler this way :\ CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x); bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value; |
检查成员变量
1 2 | CREATE_MEMBER_VAR_CHECK(x); bool has_var_x = has_member_var_x<class_to_check_for_x>::value; |
检查成员类
1 2 | CREATE_MEMBER_CLASS_CHECK(x); bool has_class_x = has_member_class_x<class_to_check_for_x>::value; |
检查是否有会员工会
1 2 | CREATE_MEMBER_UNION_CHECK(x); bool has_union_x = has_member_union_x<class_to_check_for_x>::value; |
检查成员枚举
1 2 | CREATE_MEMBER_ENUM_CHECK(x); bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value; |
检查任何成员函数
1 2 3 4 5 6 7 | CREATE_MEMBER_CHECK(x); CREATE_MEMBER_VAR_CHECK(x); CREATE_MEMBER_CLASS_CHECK(x); CREATE_MEMBER_UNION_CHECK(x); CREATE_MEMBER_ENUM_CHECK(x); CREATE_MEMBER_FUNC_CHECK(x); bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value; |
或
1 2 | CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above. bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value; |
细节和核心:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /* - Multiple inheritance forces ambiguity of member names. - SFINAE is used to make aliases to member names. - Expression SFINAE is used in just one generic has_member that can accept any alias we pass it. */ //Variadic to force ambiguity of class members. C++11 and up. template <typename... Args> struct ambiguate : public Args... {}; //Non-variadic version of the line above. //template <typename A, typename B> struct ambiguate : public A, public B {}; template<typename A, typename = void> struct got_type : std::false_type {}; template<typename A> struct got_type<A> : std::true_type { typedef A type; }; template<typename T, T> struct sig_check : std::true_type {}; template<typename Alias, typename AmbiguitySeed> struct has_member { template<typename C> static char ((&f(decltype(&C::value))))[1]; template<typename C> static char ((&f(...)))[2]; //Make sure the member name is consistently spelled the same. static_assert( (sizeof(f<AmbiguitySeed>(0)) == 1) ,"Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified." ); static bool const value = sizeof(f<Alias>(0)) == 2; }; |
宏(el diablo!):
创建成员检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //Check for any member with given name, whether var, func, class, union, enum. #define CREATE_MEMBER_CHECK(member) \ \ template<typename T, typename = std::true_type> \ struct Alias_##member; \ \ template<typename T> \ struct Alias_##member < \ T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> \ > { static const decltype(&T::member) value; }; \ \ struct AmbiguitySeed_##member { char member; }; \ \ template<typename T> \ struct has_member_##member { \ static const bool value \ = has_member< \ Alias_##member> \ , Alias_##member<AmbiguitySeed_##member> \ >::value \ ; \ } |
创建成员变量检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //Check for member variable with given name. #define CREATE_MEMBER_VAR_CHECK(var_name) \ \ template<typename T, typename = std::true_type> \ struct has_member_var_##var_name : std::false_type {}; \ \ template<typename T> \ struct has_member_var_##var_name< \ T \ , std::integral_constant< \ bool \ , !std::is_member_function_pointer<decltype(&T::var_name)>::value \ > \ > : std::true_type {} |
创建成员检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 | //Check for member function with given name AND signature. #define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \ \ template<typename T, typename = std::true_type> \ struct has_member_func_##templ_postfix : std::false_type {}; \ \ template<typename T> \ struct has_member_func_##templ_postfix< \ T, std::integral_constant< \ bool \ , sig_check<func_sig, &T::func_name>::value \ > \ > : std::true_type {} |
创建u成员u类u检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //Check for member class with given name. #define CREATE_MEMBER_CLASS_CHECK(class_name) \ \ template<typename T, typename = std::true_type> \ struct has_member_class_##class_name : std::false_type {}; \ \ template<typename T> \ struct has_member_class_##class_name< \ T \ , std::integral_constant< \ bool \ , std::is_class< \ typename got_type<typename T::class_name>::type \ >::value \ > \ > : std::true_type {} |
创建成员联合检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //Check for member union with given name. #define CREATE_MEMBER_UNION_CHECK(union_name) \ \ template<typename T, typename = std::true_type> \ struct has_member_union_##union_name : std::false_type {}; \ \ template<typename T> \ struct has_member_union_##union_name< \ T \ , std::integral_constant< \ bool \ , std::is_union< \ typename got_type<typename T::union_name>::type \ >::value \ > \ > : std::true_type {} |
创建成员枚举检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //Check for member enum with given name. #define CREATE_MEMBER_ENUM_CHECK(enum_name) \ \ template<typename T, typename = std::true_type> \ struct has_member_enum_##enum_name : std::false_type {}; \ \ template<typename T> \ struct has_member_enum_##enum_name< \ T \ , std::integral_constant< \ bool \ , std::is_enum< \ typename got_type<typename T::enum_name>::type \ >::value \ > \ > : std::true_type {} |
创建成员检查:
1 2 3 4 5 6 7 8 9 10 11 12 | //Check for function with given name, any signature. #define CREATE_MEMBER_FUNC_CHECK(func) \ template<typename T> \ struct has_member_func_##func { \ static const bool value \ = has_member_##func<T>::value \ && !has_member_var_##func<T>::value \ && !has_member_class_##func<T>::value \ && !has_member_union_##func<T>::value \ && !has_member_enum_##func<T>::value \ ; \ } |
创建成员检查:
1 2 3 4 5 6 7 8 | //Create all the checks for one member. Does NOT include func sig checks. #define CREATE_MEMBER_CHECKS(member) \ CREATE_MEMBER_CHECK(member); \ CREATE_MEMBER_VAR_CHECK(member); \ CREATE_MEMBER_CLASS_CHECK(member); \ CREATE_MEMBER_UNION_CHECK(member); \ CREATE_MEMBER_ENUM_CHECK(member); \ CREATE_MEMBER_FUNC_CHECK(member) |
如果该方法恰好在基类中定义,则由LITB提出的标准C++解决方案将无法正常工作。
有关处理此情况的解决方案,请参阅:
俄语:网址:http://www.rsdn.ru/forum/message/2759773.1.aspx
罗马英语翻译。佩雷佩利萨:http://groups.google.com/group/comp.lang.c++.modarded/tree/browse frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?PLI=1
它非常聪明。然而,这个解决方案的一个问题是,如果被测试的类型不能用作基类(例如,基元类型),则会出现编译器错误。
在Visual Studio中,我注意到,如果使用没有参数的方法,则需要在参数周围插入一对多余的(),以便在sizeof表达式中推导()。
现在这是一个很好的小谜题-很好的问题!
这里有一个替代尼科拉·博内利的解决方案,它不依赖于非标准的
不幸的是,它在GCC(MIWW)3.4.5或数字MARS 842N上不起作用,但是它对所有版本的MSVC(包括VC6)和COMUO C++都起作用。
较长的注释块具有它如何工作(或应该如何工作)的详细信息。正如上面所说,我不确定哪种行为符合标准——我欢迎对此发表评论。
更新-2008年11月7日:
看起来,虽然这个代码在语法上是正确的,但是MSVC和COMUO C++显示的行为不符合标准(感谢Leon Timmermans和LITB指出我在正确的方向)。C++ 03标准如下:
14.6.2 Dependent names [temp.dep]
Paragraph 3
In the definition of a class template
or a member of a class template, if a
base class of the class template
depends on a template-parameter, the
base class scope is not examined
during unqualified name lookup either
at the point of definition of the
class template or member or during an
instantiation of the class template or
member.
因此,当msvc或comeau认为
gcc和digital mars的行为看起来是正确的-在这两种情况下,非成员
老鼠-我想我可能找到了一个聪明的解决方案,相反,我发现了几个编译器错误…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | #include <iostream> #include <string> struct Hello { std::string toString() { return"Hello"; } }; struct Generic {}; // the following namespace keeps the toString() method out of // most everything - except the other stuff in this // compilation unit namespace { std::string toString() { return"toString not defined"; } template <typename T> class optionalToStringImpl : public T { public: std::string doToString() { // in theory, the name lookup for this call to // toString() should find the toString() in // the base class T if one exists, but if one // doesn't exist in the base class, it'll // find the free toString() function in // the private namespace. // // This theory works for MSVC (all versions // from VC6 to VC9) and Comeau C++, but // does not work with MinGW 3.4.5 or // Digital Mars 8.42n // // I'm honestly not sure what the standard says // is the correct behavior here - it's sort // of like ADL (Argument Dependent Lookup - // also known as Koenig Lookup) but without // arguments (except the implied"this" pointer) return toString(); } }; } template <typename T> std::string optionalToString(T & obj) { // ugly, hacky cast... optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj); return temp->doToString(); } int main(int argc, char *argv[]) { Hello helloObj; Generic genericObj; std::cout << optionalToString( helloObj) << std::endl; std::cout << optionalToString( genericObj) << std::endl; return 0; } |
我在另一个线程中为此编写了一个答案(与上面的解决方案不同),该线程还检查继承的成员函数:
检查继承的成员函数
下面是该解决方案的一些示例:
实例1:我们正在检查是否有以下签名的成员:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | template<class T> struct has_const_begin { typedef char (&Yes)[1]; typedef char (&No)[2]; template<class U> static Yes test(U const * data, typename std::enable_if<std::is_same< typename U::const_iterator, decltype(data->begin()) >::value>::type * = 0); static No test(...); static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0)); }; |
请注意,它甚至会检查方法的结构,并与基元类型一起使用。(我的意思是
现在我们在找签名:
1 2 3 4 5 6 7 8 9 10 11 12 13 | template<class T> struct has_foo { typedef char (&Yes)[1]; typedef char (&No)[2]; template<class U> static Yes test(U * data, MyClass* arg1 = 0, typename std::enable_if<std::is_void< decltype(data->foo(*arg1, 1u)) >::value>::type * = 0); static No test(...); static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0)); }; |
请注意,MyClass不必是默认可构造的,也不必满足任何特殊的概念。该技术也适用于模板成员。
我热切地等待关于这一点的意见。
msvc有"如果存在"和"如果不存在"关键字(doc)。结合尼古拉的sfinae方法类型,我可以创建一个检查gcc和msvc,就像OP所寻找的那样。
更新:可以在此处找到源
我修改了https://stackoverflow.com/a/264088/2712152中提供的解决方案,使其更加通用。此外,由于它不使用任何新的C++ 11特性,所以我们可以使用它来与旧编译器一起使用,也应该使用MSVC。但是编译器应该允许C99使用它,因为它使用可变宏。
以下宏可用于检查特定类是否具有特定的typedef。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /** * @class : HAS_TYPEDEF * @brief : This macro will be used to check if a class has a particular * typedef or not. * @param typedef_name : Name of Typedef * @param name : Name of struct which is going to be run the test for * the given particular typedef specified in typedef_name */ #define HAS_TYPEDEF(typedef_name, name) \ template <typename T> \ struct name { \ typedef char yes[1]; \ typedef char no[2]; \ template <typename U> \ struct type_check; \ template <typename _1> \ static yes& chk(type_check<typename _1::typedef_name>*); \ template <typename> \ static no& chk(...); \ static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \ } |
以下宏可用于检查特定类是否具有特定的成员函数以及给定数量的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * @class : HAS_MEM_FUNC * @brief : This macro will be used to check if a class has a particular * member function implemented in the public section or not. * @param func : Name of Member Function * @param name : Name of struct which is going to be run the test for * the given particular member function name specified in func * @param return_type: Return type of the member function * @param ellipsis(...) : Since this is macro should provide test case for every * possible member function we use variadic macros to cover all possibilities */ #define HAS_MEM_FUNC(func, name, return_type, ...) \ template <typename T> \ struct name { \ typedef return_type (T::*Sign)(__VA_ARGS__); \ typedef char yes[1]; \ typedef char no[2]; \ template <typename U, U> \ struct type_check; \ template <typename _1> \ static yes& chk(type_check<Sign, &_1::func>*); \ template <typename> \ static no& chk(...); \ static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \ } |
我们可以使用以上两个宏来执行has typedef和has mem func的检查,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | class A { public: typedef int check; void check_function() {} }; class B { public: void hello(int a, double b) {} void hello() {} }; HAS_MEM_FUNC(check_function, has_check_function, void, void); HAS_MEM_FUNC(hello, hello_check, void, int, double); HAS_MEM_FUNC(hello, hello_void_check, void, void); HAS_TYPEDEF(check, has_typedef_check); int main() { std::cout <<"Check Function A:" << has_check_function<A>::value << std::endl; std::cout <<"Check Function B:" << has_check_function::value << std::endl; std::cout <<"Hello Function A:" << hello_check<A>::value << std::endl; std::cout <<"Hello Function B:" << hello_check::value << std::endl; std::cout <<"Hello void Function A:" << hello_void_check<A>::value << std::endl; std::cout <<"Hello void Function B:" << hello_void_check::value << std::endl; std::cout <<"Check Typedef A:" << has_typedef_check<A>::value << std::endl; std::cout <<"Check Typedef B:" << has_typedef_check::value << std::endl; } |
使用sfinae和模板部分专用化的示例,通过编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #include <type_traits> struct A{}; struct B{ int foo(int a, int b);}; struct C{void foo(int a, int b);}; struct D{int foo();}; struct E: public B{}; // available in C++17 onwards as part of <type_traits> template<typename...> using void_t = void; template<typename T, typename = void> struct Has_foo: std::false_type{}; template<typename T> struct Has_foo<T, void_t< std::enable_if_t< std::is_same< int, decltype(std::declval<T>().foo((int)0, (int)0)) >::value > >>: std::true_type{}; static_assert(not Has_foo<A>::value,"A does not have a foo"); static_assert(Has_foo::value,"B has a foo"); static_assert(not Has_foo<C>::value,"C has a foo with the wrong return."); static_assert(not Has_foo<D>::value,"D has a foo with the wrong arguments."); static_assert(Has_foo<E>::value,"E has a foo since it inherits from B"); |
可用于检查类型是否支持某些"功能"的通用模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <type_traits> template <template <typename> class TypeChecker, typename Type> struct is_supported { // these structs are used to recognize which version // of the two functions was chosen during overload resolution struct supported {}; struct not_supported {}; // this overload of chk will be ignored by SFINAE principle // if TypeChecker<Type_> is invalid type template <typename Type_> static supported chk(typename std::decay<TypeChecker<Type_>>::type *); // ellipsis has the lowest conversion rank, so this overload will be // chosen during overload resolution only if the template overload above is ignored template <typename Type_> static not_supported chk(...); // if the template overload of chk is chosen during // overload resolution then the feature is supported // if the ellipses overload is chosen the the feature is not supported static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value; }; |
检查是否有与签名
1 2 3 4 5 | // if T doesn't have foo method with the signature that allows to compile the bellow // expression then instantiating this template is Substitution Failure (SF) // which Is Not An Error (INAE) if this happens during overload resolution template <typename T> using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>()))); |
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // types that support has_foo struct struct1 { double foo(const char*); }; // exact signature match struct struct2 { int foo(const std::string &str); }; // compatible signature struct struct3 { float foo(...); }; // compatible ellipsis signature struct struct4 { template <typename T> int foo(T t); }; // compatible template signature // types that do not support has_foo struct struct5 { void foo(const char*); }; // returns void struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double struct struct7 { double foo( int *); }; // const char* can't be converted to int* struct struct8 { double bar(const char*); }; // there is no foo method int main() { std::cout << std::boolalpha; std::cout << is_supported<has_foo, int >::value << std::endl; // false std::cout << is_supported<has_foo, double >::value << std::endl; // false std::cout << is_supported<has_foo, struct1>::value << std::endl; // true std::cout << is_supported<has_foo, struct2>::value << std::endl; // true std::cout << is_supported<has_foo, struct3>::value << std::endl; // true std::cout << is_supported<has_foo, struct4>::value << std::endl; // true std::cout << is_supported<has_foo, struct5>::value << std::endl; // false std::cout << is_supported<has_foo, struct6>::value << std::endl; // false std::cout << is_supported<has_foo, struct7>::value << std::endl; // false std::cout << is_supported<has_foo, struct8>::value << std::endl; // false return 0; } |
http://coliru.stacked-crooked.com/a/83c6a631ed42cea4
奇怪的是,没有人建议我在这个网站上看到过以下好的技巧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | template <class T> struct has_foo { struct S { void foo(...); }; struct derived : S, T {}; template <typename V, V> struct W {}; template <typename X> char (&test(W<void (X::*)(), &X::foo> *))[1]; template <typename> char (&test(...))[2]; static const bool value = sizeof(test<derived>(0)) == 1; }; |
你必须确保T是一门课。似乎查找foo中的歧义是一种替换失败。我让它在GCC上工作,但不确定它是否是标准的。
这里有很多答案,但是我失败了,找到了一个执行实际方法解析排序的版本,而没有使用任何新的C++特性(只使用C++ 98个特性)。注意:此版本通过VC++2013、G++5.2.0和联机编译器进行测试和工作。
所以我想出了一个版本,只使用sizeof():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | template<typename T> T declval(void); struct fake_void { }; template<typename T> T &operator,(T &,fake_void); template<typename T> T const &operator,(T const &,fake_void); template<typename T> T volatile &operator,(T volatile &,fake_void); template<typename T> T const volatile &operator,(T const volatile &,fake_void); struct yes { char v[1]; }; struct no { char v[2]; }; template<bool> struct yes_no:yes{}; template<> struct yes_no<false>:no{}; template<typename T> struct has_awesome_member { template<typename U> static yes_no<(sizeof(( declval<U>().awesome_member(),fake_void() ))!=0)> check(int); template<typename> static no check(...); enum{value=sizeof(check<T>(0)) == sizeof(yes)}; }; struct foo { int awesome_member(void); }; struct bar { }; struct foo_void { void awesome_member(void); }; struct wrong_params { void awesome_member(int); }; static_assert(has_awesome_member<foo>::value,""); static_assert(!has_awesome_member<bar>::value,""); static_assert(has_awesome_member<foo_void>::value,""); static_assert(!has_awesome_member<wrong_params>::value,""); |
实时演示(带扩展返回类型检查和VC++2010解决方案):http://cpp.sh/5b2vs
没有消息来源,因为我自己想出的。
在G++编译器上运行实时演示时,请注意允许数组大小为0,这意味着使用的静态断言不会触发编译器错误,即使失败也不会触发。常用的解决方法是将宏中的"typedef"替换为"extern"。
这里是我的版本,它使用任意arity处理所有可能的成员函数重载,包括模板成员函数,可能使用默认参数。在对某个类类型进行成员函数调用时,它区分了3种互斥的情况,其中给定的arg类型有:(1)有效的,或(2)不明确的,或(3)不可行的。示例用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <string> #include <vector> HAS_MEM(bar) HAS_MEM_FUN_CALL(bar) struct test { void bar(int); void bar(double); void bar(int,double); template < typename T > typename std::enable_if< not std::is_integral<T>::value >::type bar(const T&, int=0){} template < typename T > typename std::enable_if< std::is_integral<T>::value >::type bar(const std::vector<T>&, T*){} template < typename T > int bar(const std::string&, int){} }; |
现在您可以这样使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | int main(int argc, const char * argv[]) { static_assert( has_mem_bar<test>::value ,""); static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value ,""); static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value ,""); static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value ,""); static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value ,""); static_assert( has_valid_mem_fun_call_bar<test(int)>::value ,""); static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value ,""); static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value ,""); static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value ,""); static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value ,""); static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value ,""); static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value ,""); static_assert( has_viable_mem_fun_call_bar<test(int)>::value ,""); static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value ,""); return 0; } |
这里是代码,用C++ 11编写,但是,您可以很容易地将它(带有小的调整)移植到具有类型扩展(例如GCC)的非C++ 11。您可以用自己的宏替换has-mem宏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | #pragma once #if __cplusplus >= 201103 #include <utility> #include <type_traits> #define HAS_MEM(mem) \ \ template < typename T > \ struct has_mem_##mem \ { \ struct yes {}; \ struct no {}; \ \ struct ambiguate_seed { char mem; }; \ template < typename U > struct ambiguate : U, ambiguate_seed {}; \ \ template < typename U, typename = decltype(&U::mem) > static constexpr no test(int); \ template < typename > static constexpr yes test(...); \ \ static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ; \ typedef std::integral_constant<bool,value> type; \ }; #define HAS_MEM_FUN_CALL(memfun) \ \ template < typename Signature > \ struct has_valid_mem_fun_call_##memfun; \ \ template < typename T, typename... Args > \ struct has_valid_mem_fun_call_##memfun< T(Args...) > \ { \ struct yes {}; \ struct no {}; \ \ template < typename U, bool = has_mem_##memfun<U>::value > \ struct impl \ { \ template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \ struct test_result { using type = yes; }; \ \ template < typename V > static constexpr typename test_result<V>::type test(int); \ template < typename > static constexpr no test(...); \ \ static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value; \ using type = std::integral_constant<bool, value>; \ }; \ \ template < typename U > \ struct impl<U,false> : std::false_type {}; \ \ static constexpr bool value = impl<T>::value; \ using type = std::integral_constant<bool, value>; \ }; \ \ template < typename Signature > \ struct has_ambiguous_mem_fun_call_##memfun; \ \ template < typename T, typename... Args > \ struct has_ambiguous_mem_fun_call_##memfun< T(Args...) > \ { \ struct ambiguate_seed { void memfun(...); }; \ \ template < class U, bool = has_mem_##memfun<U>::value > \ struct ambiguate : U, ambiguate_seed \ { \ using ambiguate_seed::memfun; \ using U::memfun; \ }; \ \ template < class U > \ struct ambiguate<U,false> : ambiguate_seed {}; \ \ static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \ using type = std::integral_constant<bool, value>; \ }; \ \ template < typename Signature > \ struct has_viable_mem_fun_call_##memfun; \ \ template < typename T, typename... Args > \ struct has_viable_mem_fun_call_##memfun< T(Args...) > \ { \ static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value \ or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value; \ using type = std::integral_constant<bool, value>; \ }; \ \ template < typename Signature > \ struct has_no_viable_mem_fun_call_##memfun; \ \ template < typename T, typename... Args > \ struct has_no_viable_mem_fun_call_##memfun < T(Args...) > \ { \ static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value; \ using type = std::integral_constant<bool, value>; \ }; \ \ template < typename Signature > \ struct result_of_mem_fun_call_##memfun; \ \ template < typename T, typename... Args > \ struct result_of_mem_fun_call_##memfun< T(Args...) > \ { \ using type = decltype(std::declval<T>().memfun(std::declval<Args>()...)); \ }; #endif |
您可以跳过C++ 14中的所有元编程,只需从FIT库中使用EDCOX1 OR 4来编写此程序:
1 2 3 4 5 6 7 8 | template<class T> std::string optionalToString(T* x) { return fit::conditional( [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); }, [](auto*) { return"toString not defined"; } )(x); } |
也可以直接从lambda创建函数:
1 2 3 4 | FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional( [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); }, [](auto*) -> std::string { return"toString not defined"; } ); |
但是,如果使用的编译器不支持通用lambda,则必须编写单独的函数对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | struct withToString { template<class T> auto operator()(T* obj) const -> decltype(obj->toString(), std::string()) { return obj->toString(); } }; struct withoutToString { template<class T> std::string operator()(T*) const { return"toString not defined"; } }; FIT_STATIC_FUNCTION(optionalToString) = fit::conditional( withToString(), withoutToString() ); |
这个解决方案怎么样?
1 2 3 4 5 6 7 8 | #include <type_traits> template <typename U, typename = void> struct hasToString : std::false_type { }; template <typename U> struct hasToString<U, typename std::enable_if<bool(sizeof(&U::toString))>::type > : std::true_type { }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 | template<class T> auto optionalToString(T* obj) ->decltype( obj->toString(), std::string() ) { return obj->toString(); } template<class T> auto optionalToString(T* obj) ->decltype( std::string() ) { throw"Error!"; } |
下面是一个工作代码的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | template<typename T> using toStringFn = decltype(std::declval<const T>().toString()); template <class T, toStringFn<T>* = nullptr> std::string optionalToString(const T* obj, int) { return obj->toString(); } template <class T> std::string optionalToString(const T* obj, long) { return"toString not defined"; } int main() { A* a; B* b; std::cout << optionalToString(a, 0) << std::endl; // This is A std::cout << optionalToString(b, 0) << std::endl; // toString not defined } |
如果函数已实现,则可以对返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | template <typename T> constexpr bool toStringExists(long) { return false; } template <typename T, toStringFn<T>* = nullptr> constexpr bool toStringExists(int) { return true; } int main() { A* a; B* b; std::cout << toStringExists<A>(0) << std::endl; // true std::cout << toStringExists(0) << std::endl; // false } |
我也有类似的问题:
一种模板类,它可以从几个基本类派生,一些基本类有一个特定的成员,而另一些基本类没有。
我用类似于"typeof"(Nicola Bonelli的)的答案来解决这个问题,但是使用decltype,它可以在MSV上正确编译和运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include <iostream> #include <string> struct Generic {}; struct HasMember { HasMember() : _a(1) {}; int _a; }; // SFINAE test template <typename T> class S : public T { public: std::string foo (std::string b) { return foo2<T>(b,0); } protected: template <typename T> std::string foo2 (std::string b, decltype (T::_a)) { return b + std::to_string(T::_a); } template <typename T> std::string foo2 (std::string b, ...) { return b +"No"; } }; int main(int argc, char *argv[]) { S<HasMember> d1; S<Generic> d2; std::cout << d1.foo("HasMember:") << std::endl; std::cout << d2.foo("Generic:") << std::endl; return 0; } |