检查一个类是否有给定签名的成员函数
我要求一个模板技巧来检测一个类是否具有给定签名的特定成员函数。
这个问题类似于http://www.gotw.ca/gotw/071.htm中的一个,但不一样:在Sutter的书中,他回答了C类必须提供成员函数的问题一个特殊的签名,否则程序将不能编译。 在我的问题,我需要做一些事情,如果一个类有这个function,否则做“别的东西”。
boost :: serialization也面临类似的问题,但我不喜欢他们采用的解决scheme:一个模板函数默认调用一个自由函数(你必须定义)一个特定的签名,除非你定义一个特定的成员函数在他们的情况下,使用特定types的2个参数进行“序列化”),否则会发生编译错误。 那就是实现侵入式和非侵入式序列化。
我不喜欢这个解决scheme有两个原因:
- 要非侵入性,你必须覆盖boost :: serialization命名空间中的全局“serialize”函数,所以你有你的客户端代码来打开命名空间提升和命名空间序列化!
- 解决这个混乱的堆栈是10到12个函数调用。
我需要为没有该成员函数的类定义一个自定义行为,并且我的实体位于不同的名称空间中(并且我不想在另一个名称空间中重写在一个名称空间中定义的全局函数)
你能给我一个解决这个难题的提示吗?
我不确定我是否正确理解你,但是你可能会利用SFINAE在编译时检测函数的存在。 从我的代码示例(testing类是否有成员函数size_t used_memory()const)。
template<typename T> struct HasUsedMemoryMethod { template<typename U, size_t (U::*)() const> struct SFINAE {}; template<typename U> static char Test(SFINAE<U, &U::used_memory>*); template<typename U> static int Test(...); static const bool Has = sizeof(Test<T>(0)) == sizeof(char); }; template<typename TMap> void ReportMemUsage(const TMap& m, std::true_type) { // We may call used_memory() on m here. } template<typename TMap> void ReportMemUsage(const TMap&, std::false_type) { } template<typename TMap> void ReportMemUsage(const TMap& m) { ReportMemUsage(m, std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>()); }
这是一个依赖于C ++ 11function的可能实现。 它正确地检测function,即使它是inheritance的(不同于被接受的答案中的解决scheme,正如Mike Kinghan在他的回答中所看到的)。
这段代码testing的函数被称为serialize
:
#include <type_traits> // Primary template with a static assertion // for a meaningful error message // if it ever gets instantiated. // We could leave it undefined if we didn't care. template<typename, typename T> struct has_serialize { static_assert( std::integral_constant<T, false>::value, "Second template parameter needs to be of function type."); }; // specialization that does the checking template<typename C, typename Ret, typename... Args> struct has_serialize<C, Ret(Args...)> { private: template<typename T> static constexpr auto check(T*) -> typename std::is_same< decltype( std::declval<T>().serialize( std::declval<Args>()... ) ), Ret // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >::type; // attempt to call it and see if the return type is correct template<typename> static constexpr std::false_type check(...); typedef decltype(check<C>(0)) type; public: static constexpr bool value = type::value; };
用法:
struct X { int serialize(const std::string&) { return 42; } }; struct Y : X {}; std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1
这个编译时成员函数内省这个问题的公认的答案虽然很stream行,但在以下程序中可以观察到一个障碍:
#include <type_traits> #include <iostream> #include <memory> /* Here we apply the accepted answer's technique to probe for the the existence of `ET::operator*() const` */ template<typename T, typename E> struct has_const_reference_op { template<typename U, E (U::*)() const> struct SFINAE {}; template<typename U> static char Test(SFINAE<U, &U::operator*>*); template<typename U> static int Test(...); static const bool value = sizeof(Test<T>(0)) == sizeof(char); }; using namespace std; /* Here we test the `std::` smart pointer templates, including the deprecated `auto_ptr<T>`, to determine in each case whether T = (the template instantiated for `int`) provides `int & T::operator*() const` - which all of them in fact do. */ int main(void) { cout << has_const_reference_op<auto_ptr<int>,int &>::value; cout << has_const_reference_op<unique_ptr<int>,int &>::value; cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl; return 0; }
用GCC 4.6.3构build,程序输出110
– 通知我们T = std::shared_ptr<int>
不提供int & T::operator*() const
。
如果你还没有明白这个问题,那么看看头文件<memory>
中std::shared_ptr<T>
的定义就会显示出来。 在那个实现中, std::shared_ptr<T>
是从inheritanceoperator*() const
的基类派生的。 因此std::shared_ptr<T>
中没有operator*()
它自己的权利和模板实例化不会“inheritance”。
这个障碍不会影响着名的SFINAE方法,使用“sizeof()技巧”来检测T
是否具有某些成员函数mf
(参见这个答案和注释)。 但是确定T::mf
存在通常(通常?)还不够好:您可能还需要确定它是否具有所需的签名。 这是所示技术得分的地方。 所需签名的指针化变体被写入模板types的参数中,该参数必须由&T::mf
满足,以使SFINAE探针成功。 但是当T::mf
被inheritance时,这个模板实例化技术给出了错误的答案。
一个安全的SFINAE技术用于对T::mf
进行编译时自省,必须避免在模板参数中使用&T::mf
来实例化SFINAE函数模板parsing所依赖的types。 相反,SFINAE模板函数parsing只能依赖于恰好相关的types声明,用作重载的SFINAE探测函数的参数types。
通过对遵守这个约束的问题的回答,我将说明ET::operator*() const
对于任意T
和E
编译时检测。 相同的模式将比照适用于探测任何其他成员方法签名。
#include <type_traits> /*! The template `has_const_reference_op<T,E>` exports a boolean constant `value that is true iff `T` provides `ET::operator*() const` */ template< typename T, typename E> struct has_const_reference_op { /* SFINAE operator-has-correct-sig :) */ template<typename A> static std::true_type test(E (A::*)() const) { return std::true_type(); } /* SFINAE operator-exists :) */ template <typename A> static decltype(test(&A::operator*)) test(decltype(&A::operator*),void *) { /* Operator exists. What about sig? */ typedef decltype(test(&A::operator*)) return_type; return return_type(); } /* SFINAE game over :( */ template<typename A> static std::false_type test(...) { return std::false_type(); } /* This will be either `std::true_type` or `std::false_type` */ typedef decltype(test<T>(0,0)) type; static const bool value = type::value; /* Which is it? */ };
在这个解决scheme中,重载的SFINAE探测函数test()
是“recursion调用的”。 (当然,它实际上并没有被调用;它只是具有由编译器parsing的返回types的假设调用。)
我们需要探究至less一个和最多两个信息点:
-
T::operator*()
是否存在? 如果没有,我们就完成了。 - 考虑到
T::operator*()
存在,是它的签名ET::operator*() const
?
我们通过评估一次调用的返回typestest(0,0)
来得到答案。 这是通过:
typedef decltype(test<T>(0,0)) type;
这个调用可能会被parsing为/* SFINAE operator-exists :) */
test()
超载,或者它可能会parsing为/* SFINAE game over :( */
overload。它无法parsing为/* SFINAE operator-has-correct-sig :) */
overload,因为那个只要一个参数,而且我们传递了两个参数。
为什么我们两个? 只是强制解决scheme排除/* SFINAE operator-has-correct-sig :) */
。 第二个论据没有其他意义。
这个test(0,0)
调用将parsing为/* SFINAE operator-exists :) */
以防万一第一个参数0满足该重载的第一个参数types,即decltype(&A::operator*)
, A = T
0将满足该types,以防T::operator*
存在。
我们假设编译器对此表示“是”。 然后它是用/* SFINAE operator-exists :) */
它需要确定函数调用的返回types,在这种情况下,它是decltype(test(&A::operator*))
– 另一个返回types调用test()
。
这一次,我们只传递了一个参数, &A::operator*
,我们现在知道存在,或者我们不会在这里。 呼叫test(&A::operator*)
可能会解决/* SFINAE operator-has-correct-sig :) */
或再次可能解决/* SFINAE game over :( */
。电话将匹配/* SFINAE operator-has-correct-sig :) */
以防万一&A::operator*
满足该重载的单个参数types,即E (A::*)() const
,其中A = T
如果T::operator*
具有所需的签名,那么编译器会在这里说“是”,然后再次评估重载的返回types。 现在没有更多的“recursion”:它是std::true_type
。
如果编译器没有select/* SFINAE operator-exists :) */
用于呼叫test(0,0)
或者不select/* SFINAE operator-has-correct-sig :) */
用于呼叫test(&A::operator*)
,那么无论哪种情况下,都会使用/* SFINAE game over :( */
,最终的返回types是std::false_type
。
这里是一个testing程序,显示在不同样本的案例中产生预期答案的模板(再次GCC 4.6.3)。
// To test struct empty{}; // To test struct int_ref { int & operator*() const { return *_pint; } int & foo() const { return *_pint; } int * _pint; }; // To test struct sub_int_ref : int_ref{}; // To test template<typename E> struct ee_ref { E & operator*() { return *_pe; } E & foo() const { return *_pe; } E * _pe; }; // To test struct sub_ee_ref : ee_ref<char>{}; using namespace std; #include <iostream> #include <memory> #include <vector> int main(void) { cout << "Expect Yes" << endl; cout << has_const_reference_op<auto_ptr<int>,int &>::value; cout << has_const_reference_op<unique_ptr<int>,int &>::value; cout << has_const_reference_op<shared_ptr<int>,int &>::value; cout << has_const_reference_op<std::vector<int>::iterator,int &>::value; cout << has_const_reference_op<std::vector<int>::const_iterator, int const &>::value; cout << has_const_reference_op<int_ref,int &>::value; cout << has_const_reference_op<sub_int_ref,int &>::value << endl; cout << "Expect No" << endl; cout << has_const_reference_op<int *,int &>::value; cout << has_const_reference_op<unique_ptr<int>,char &>::value; cout << has_const_reference_op<unique_ptr<int>,int const &>::value; cout << has_const_reference_op<unique_ptr<int>,int>::value; cout << has_const_reference_op<unique_ptr<long>,int &>::value; cout << has_const_reference_op<int,int>::value; cout << has_const_reference_op<std::vector<int>,int &>::value; cout << has_const_reference_op<ee_ref<int>,int &>::value; cout << has_const_reference_op<sub_ee_ref,int &>::value; cout << has_const_reference_op<empty,int &>::value << endl; return 0; }
这个想法有新的缺陷吗? 它可以变得更通用而不会再次陷入它所避免的障碍之中吗?
如果您知道您期望的成员函数的名称,这应该是足够的。 (在这种情况下,函数bla无法实例化,如果没有成员函数(编写一个无论如何工作是艰难的,因为缺乏function部分专业化),而且,可能需要使用类模板。类似于enable_if)也可以模板化你想要它作为一个成员的函数的types。
template <typename T, int (T::*) ()> struct enable { typedef T type; }; template <typename T> typename enable<T, &T::i>::type bla (T&); struct A { void i(); }; struct B { int i(); }; int main() { A a; B b; bla(b); bla(a); }
以下是一些使用片段:*所有这些的胆量越来越低
检查给定类中的成员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;
检查成员variablesx
:
CREATE_MEMBER_VAR_CHECK(x); bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
检查会员类别x
:
CREATE_MEMBER_CLASS_CHECK(x); bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
检查会员联盟x
:
CREATE_MEMBER_UNION_CHECK(x); bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
检查成员枚举x
:
CREATE_MEMBER_ENUM_CHECK(x); bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
无论签名如何,检查任何成员函数x
:
CREATE_MEMBER_CHECK(x); CREATE_MEMBER_VAR_CHECK(x); CREATE_MEMBER_CLASS_CHECK(x); CREATE_MEMBER_UNION_CHECK(x); CREATE_MEMBER_ENUM_CHECK(x); CREATE_MEMBER_FUNC_CHECK(x); bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
要么
CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above. bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
细节和核心:
/* - Multiple inheritance forces ambiguity of member names. - SFINAE is used to make aliases to member names. - Expression SFINAE is used in just one generic has_member that can accept any alias we pass it. */ //Variadic to force ambiguity of class members. C++11 and up. template <typename... Args> struct ambiguate : public Args... {}; //Non-variadic version of the line above. //template <typename A, typename B> struct ambiguate : public A, public B {}; template<typename A, typename = void> struct got_type : std::false_type {}; template<typename A> struct got_type<A> : std::true_type { typedef A type; }; template<typename T, T> struct sig_check : std::true_type {}; template<typename Alias, typename AmbiguitySeed> struct has_member { template<typename C> static char ((&f(decltype(&C::value))))[1]; template<typename C> static char ((&f(...)))[2]; //Make sure the member name is consistently spelled the same. static_assert( (sizeof(f<AmbiguitySeed>(0)) == 1) , "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified." ); static bool const value = sizeof(f<Alias>(0)) == 2; };
macros(El Diablo!):
CREATE_MEMBER_CHECK:
//Check for any member with given name, whether var, func, class, union, enum. #define CREATE_MEMBER_CHECK(member) \ \ template<typename T, typename = std::true_type> \ struct Alias_##member; \ \ template<typename T> \ struct Alias_##member < \ T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> \ > { static const decltype(&T::member) value; }; \ \ struct AmbiguitySeed_##member { char member; }; \ \ template<typename T> \ struct has_member_##member { \ static const bool value \ = has_member< \ Alias_##member<ambiguate<T, AmbiguitySeed_##member>> \ , Alias_##member<AmbiguitySeed_##member> \ >::value \ ; \ }
CREATE_MEMBER_VAR_CHECK:
//Check for member variable with given name. #define CREATE_MEMBER_VAR_CHECK(var_name) \ \ template<typename T, typename = std::true_type> \ struct has_member_var_##var_name : std::false_type {}; \ \ template<typename T> \ struct has_member_var_##var_name< \ T \ , std::integral_constant< \ bool \ , !std::is_member_function_pointer<decltype(&T::var_name)>::value \ > \ > : std::true_type {}
CREATE_MEMBER_FUNC_SIG_CHECK:
//Check for member function with given name AND signature. #define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \ \ template<typename T, typename = std::true_type> \ struct has_member_func_##templ_postfix : std::false_type {}; \ \ template<typename T> \ struct has_member_func_##templ_postfix< \ T, std::integral_constant< \ bool \ , sig_check<func_sig, &T::func_name>::value \ > \ > : std::true_type {}
CREATE_MEMBER_CLASS_CHECK:
//Check for member class with given name. #define CREATE_MEMBER_CLASS_CHECK(class_name) \ \ template<typename T, typename = std::true_type> \ struct has_member_class_##class_name : std::false_type {}; \ \ template<typename T> \ struct has_member_class_##class_name< \ T \ , std::integral_constant< \ bool \ , std::is_class< \ typename got_type<typename T::class_name>::type \ >::value \ > \ > : std::true_type {}
CREATE_MEMBER_UNION_CHECK:
//Check for member union with given name. #define CREATE_MEMBER_UNION_CHECK(union_name) \ \ template<typename T, typename = std::true_type> \ struct has_member_union_##union_name : std::false_type {}; \ \ template<typename T> \ struct has_member_union_##union_name< \ T \ , std::integral_constant< \ bool \ , std::is_union< \ typename got_type<typename T::union_name>::type \ >::value \ > \ > : std::true_type {}
CREATE_MEMBER_ENUM_CHECK:
//Check for member enum with given name. #define CREATE_MEMBER_ENUM_CHECK(enum_name) \ \ template<typename T, typename = std::true_type> \ struct has_member_enum_##enum_name : std::false_type {}; \ \ template<typename T> \ struct has_member_enum_##enum_name< \ T \ , std::integral_constant< \ bool \ , std::is_enum< \ typename got_type<typename T::enum_name>::type \ >::value \ > \ > : std::true_type {}
CREATE_MEMBER_FUNC_CHECK:
//Check for function with given name, any signature. #define CREATE_MEMBER_FUNC_CHECK(func) \ template<typename T> \ struct has_member_func_##func { \ static const bool value \ = has_member_##func<T>::value \ && !has_member_var_##func<T>::value \ && !has_member_class_##func<T>::value \ && !has_member_union_##func<T>::value \ && !has_member_enum_##func<T>::value \ ; \ }
CREATE_MEMBER_CHECKS:
//Create all the checks for one member. Does NOT include func sig checks. #define CREATE_MEMBER_CHECKS(member) \ CREATE_MEMBER_CHECK(member); \ CREATE_MEMBER_VAR_CHECK(member); \ CREATE_MEMBER_CLASS_CHECK(member); \ CREATE_MEMBER_UNION_CHECK(member); \ CREATE_MEMBER_ENUM_CHECK(member); \ CREATE_MEMBER_FUNC_CHECK(member)
你可以使用std :: is_member_function_pointer
class A { public: void foo() {}; } bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
迈克·金汉的回答更简单一些。 这将检测inheritance的方法。 它也将检查确切的签名(不像jrok的方法允许参数转换)。
template <class C> class HasGreetMethod { template <class T> static std::true_type testSignature(void (T::*)(const char*) const); template <class T> static decltype(testSignature(&T::greet)) test(std::nullptr_t); template <class T> static std::false_type test(...); public: using type = decltype(test<C>(nullptr)); static const bool value = type::value; }; struct A { void greet(const char* name) const; }; struct Derived : A { }; static_assert(HasGreetMethod<Derived>::value, "");
可运行的例子
自己也遇到同样的问题,并发现这里提出的解决scheme非常有趣…但是需要一个解决scheme:
- 检测inheritance的函数;
- 与非C ++ 11编译器兼容(所以没有decltype)
根据BOOST的讨论 ,find另外一个提出类似这样的线索 。 以下是boost :: has_ *类的模型,提出的解决scheme的泛化为两个特征类的macros声明。
#include <boost/type_traits/is_class.hpp> #include <boost/mpl/vector.hpp> /// Has constant function /** \param func_ret_type Function return type \param func_name Function name \param ... Variadic arguments are for the function parameters */ #define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \ __DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__) /// Has non-const function /** \param func_ret_type Function return type \param func_name Function name \param ... Variadic arguments are for the function parameters */ #define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \ __DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__) // Traits content #define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...) \ template \ < typename Type, \ bool is_class = boost::is_class<Type>::value \ > \ class has_func_ ## func_name; \ template<typename Type> \ class has_func_ ## func_name<Type,false> \ {public: \ BOOST_STATIC_CONSTANT( bool, value = false ); \ typedef boost::false_type type; \ }; \ template<typename Type> \ class has_func_ ## func_name<Type,true> \ { struct yes { char _foo; }; \ struct no { yes _foo[2]; }; \ struct Fallback \ { func_ret_type func_name( __VA_ARGS__ ) \ UTILITY_OPTIONAL(func_const,const) {} \ }; \ struct Derived : public Type, public Fallback {}; \ template <typename T, T t> class Helper{}; \ template <typename U> \ static no deduce(U*, Helper \ < func_ret_type (Fallback::*)( __VA_ARGS__ ) \ UTILITY_OPTIONAL(func_const,const), \ &U::func_name \ >* = 0 \ ); \ static yes deduce(...); \ public: \ BOOST_STATIC_CONSTANT( \ bool, \ value = sizeof(yes) \ == sizeof( deduce( static_cast<Derived*>(0) ) ) \ ); \ typedef ::boost::integral_constant<bool,value> type; \ BOOST_STATIC_CONSTANT(bool, is_const = func_const); \ typedef func_ret_type return_type; \ typedef ::boost::mpl::vector< __VA_ARGS__ > args_type; \ } // Utility functions #define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ ) #define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ ) #define __UTILITY_OPTIONAL_0(...) #define __UTILITY_OPTIONAL_1(...) __VA_ARGS__
这些macros通过以下原型展开为一个特性类:
template<class T> class has_func_[func_name] { public: /// Function definition result value /** Tells if the tested function is defined for type T or not. */ static const bool value = true | false; /// Function definition result type /** Type representing the value attribute usable in http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html */ typedef boost::integral_constant<bool,value> type; /// Tested function constness indicator /** Indicates if the tested function is const or not. This value is not deduced, it is forced depending on the user call to one of the traits generators. */ static const bool is_const = true | false; /// Tested function return type /** Indicates the return type of the tested function. This value is not deduced, it is forced depending on the user's arguments to the traits generators. */ typedef func_ret_type return_type; /// Tested function arguments types /** Indicates the arguments types of the tested function. This value is not deduced, it is forced depending on the user's arguments to the traits generators. */ typedef ::boost::mpl::vector< __VA_ARGS__ > args_type; };
那么通过这种方法可以达到的典型用法是什么呢?
// We enclose the traits class into // a namespace to avoid collisions namespace ns_0 { // Next line will declare the traits class // to detect the member function void foo(int,int) const DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int); } // we can use BOOST to help in using the traits #include <boost/utility/enable_if.hpp> // Here is a function that is active for types // declaring the good member function template<typename T> inline typename boost::enable_if< ns_0::has_func_foo<T> >::type foo_bar(const T &_this_, int a=0, int b=1) { _this_.foo(a,b); } // Here is a function that is active for types // NOT declaring the good member function template<typename T> inline typename boost::disable_if< ns_0::has_func_foo<T> >::type foo_bar(const T &_this_, int a=0, int b=1) { default_foo(_this_,a,b); } // Let us declare test types struct empty { }; struct direct_foo { void foo(int,int); }; struct direct_const_foo { void foo(int,int) const; }; struct inherited_const_foo : public direct_const_foo { }; // Now anywhere in your code you can seamlessly use // the foo_bar function on any object: void test() { int a; foo_bar(a); // calls default_foo empty b; foo_bar(b); // calls default_foo direct_foo c; foo_bar(c); // calls default_foo (member function is not const) direct_const_foo d; foo_bar(d); // calls d.foo (member function is const) inherited_const_foo e; foo_bar(e); // calls e.foo (inherited member function) }
为了保持非侵入性,还可以通过Koenig查找来将serialize
到正在序列化的类的名称空间或归档类的名称空间中。 有关更多详细信息,请参阅命名空间以获取自由函数覆盖 。 🙂
打开任何给定的名称空间来实现一个免费的function是简单的错误。 (例如,你不应该打开命名空间std
来实现你自己的types的swap
,但应该使用Koenig查找。)
好的。 第二次尝试。 没关系,如果你不喜欢这个,我正在寻找更多的想法。
香草萨特的文章谈论特质。 所以你可以有一个Traits类,它的默认实例化具有后备行为,对于每个你的成员函数所在的类,traits类专门用来调用成员函数。 我相信Herb的文章提到了这样做的一种技术,以便它不涉及大量的复制和粘贴。
就像我说的,虽然,也许你不希望额外的工作涉及到“标记”类实现该成员。 在这种情况下,我正在寻找第三个解决scheme….
要做到这一点,我们需要使用:
- 函数模板根据方法是否可用而使用不同的返回types进行重载
- 为了与
type_traits
头文件中的元条件保持一致,我们需要从我们的重载中返回一个true_type
或者false_type
- 声明期望
int
的false_type
超载,并且期望Varadic参数的false_type
超载将利用: “重载parsing中省略号转换的最低优先级” - 在为
true_type
函数定义模板规范时,我们将使用declval
和decltype
使我们能够检测独立于返回types差异或方法之间重载的函数
你可以在这里看到一个实例,但是我会在下面解释它:
我想检查一个名为test
的函数的存在,它的types可以从int
转换,然后我需要声明这两个函数:
template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int); template <typename T> static false_type hasTest(...);
-
decltype(hasTest<a>(0))::value
是true
(注意,不需要创build特殊的function来处理void a::test()
重载,接受void a::test(int)
) -
decltype(hasTest<b>(0))::value
是true
(因为int
可转换为double
int b::test(double)
被接受,与返回types无关) -
decltype(hasTest<c>(0))::value
是false
(c
没有一个名为test
的方法,接受一个可以从int
转换的types,因此这个不被接受)
该解决scheme有两个缺点:
- 要求每个方法声明一对函数
- 特别是如果我们想要testing类似的名字,会造成命名空间的污染,例如我们会怎样命名一个想testing
test()
方法的函数呢?
所以这些函数应该在细节名称空间中声明是非常重要的,或者理想情况下,如果它们只能用于类,则应该由该类私下声明。 为此我写了一个macros来帮助你抽象这个信息:
#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \ template <typename T> static false_type __ ## DEFINE(...); \ template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));
你可以像这样使用它:
namespace details { FOO(test(declval<int>()), test_int) FOO(test(), test_void) }
随后调用details::test_int<a>::value
或details::test_void<a>::value
将产生true
或false
的内联代码或元编程的目的。
我相信你正在寻找的答案在这里。
http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Compile-Time
并在这里稍微填写一些例子
我使用这种技术来检测支持的ostream运算符<<是否存在于所讨论的类中,然后根据不同的代码生成不同的代码。
我不相信有可能find链接的解决scheme,但它是一个非常巧妙的把戏。 花时间了解代码,这是非常值得的。
布拉德
没有C ++ 11支持( decltype
)这可能会工作:
SSCCE
#include <iostream> using namespace std; struct A { void foo(void); }; struct Aa: public A { }; struct B { }; struct retA { int foo(void); }; struct argA { void foo(double); }; struct constA { void foo(void) const; }; struct varA { int foo; }; template<typename T> struct FooFinder { typedef char true_type[1]; typedef char false_type[2]; template<int> struct TypeSink; template<class U> static true_type &match(U); template<class U> static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *); template<class U> static false_type &test(...); enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) }; }; int main() { cout << FooFinder<A>::value << endl; cout << FooFinder<Aa>::value << endl; cout << FooFinder<B>::value << endl; cout << FooFinder<retA>::value << endl; cout << FooFinder<argA>::value << endl; cout << FooFinder<constA>::value << endl; cout << FooFinder<varA>::value << endl; }
它希望如何工作
A
, Aa
和B
是所讨论的类别, Aa
是inheritance我们正在寻找的成员的特殊types。
在FooFinder
, true_type
和false_type
是对应的C ++ 11类的替代品。 另外为了理解模板元编程,他们揭示了SFINAE-sizeof-trick的基础。
TypeSink
是一个模板结构,稍后用于将sizeof
运算符的整体结果汇集到模板实例化中以形成一个types。
match
函数是另一个SFINAEtypes的模板,没有通用的对应模块。 It can hence only be instantiated if the type of its argument matches the type it was specialized for.
Both the test
functions together with the enum declaration finally form the central SFINAE pattern. There is a generic one using an ellipsis that returns the false_type
and a counterpart with more specific arguments to take precedence.
To be able to instantiate the test
function with a template argument of T
, the match
function must be instantiated, as its return type is required to instantiate the TypeSink
argument. The caveat is that &U::foo
, being wrapped in a function argument, is not referred to from within a template argument specialization, so inherited member lookup still takes place.