是否能够编写一个模板来更改行为,具体取决因而否在类上定义了某个成员函数? html
这是我要写的一个简单示例: 算法
template<class T> std::string optionalToString(T* obj) { if (FUNCTION_EXISTS(T->toString)) return obj->toString(); else return "toString not defined"; }
所以,若是class T
已定义了toString()
,则它将使用它;不然,它将使用它。 不然,事实并不是如此。 我不知道该怎么作的神奇部分是“ FUNCTION_EXISTS”部分。 ide
这个解决方案怎么样? 函数
#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 { };
我修改了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); \ }
如下宏可用于检查特定类是否具备特定成员函数以及是否具备给定数量的参数。 ui
/** * @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进行检查: this
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; }
这是个人版本,它经过任意Arity处理全部可能的成员函数重载,包括模板成员函数,可能还有默认参数。 当使用给定的arg类型对某个类类型进行成员函数调用时,它能够区分3种互斥的方案:(1)有效,或(2)含糊,或(3)不可行。 用法示例: spa
#include <string> #include <vector> HAS_MEM(bar) HAS_MEM_FUN_CALL(bar) struct test { void bar(int); void bar(double); void bar(int,double); template < typename T > typename std::enable_if< not std::is_integral<T>::value >::type bar(const T&, int=0){} template < typename T > typename std::enable_if< std::is_integral<T>::value >::type bar(const std::vector<T>&, T*){} template < typename T > int bar(const std::string&, int){} };
如今您能够像这样使用它: 指针
int main(int argc, const char * argv[]) { static_assert( has_mem_bar<test>::value , ""); static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , ""); static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , ""); static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , ""); static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , ""); static_assert( has_valid_mem_fun_call_bar<test(int)>::value , ""); static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , ""); static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , ""); static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , ""); static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , ""); static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , ""); static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , ""); static_assert( has_viable_mem_fun_call_bar<test(int)>::value , ""); static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , ""); return 0; }
这是用c ++ 11编写的代码,可是,您能够轻松地(稍做调整)将其移植到具备typeof扩展名的非c ++ 11(例如gcc)。 您可使用本身的宏替换HAS_MEM宏。
#pragma once #if __cplusplus >= 201103 #include <utility> #include <type_traits> #define HAS_MEM(mem) \ \ template < typename T > \ struct has_mem_##mem \ { \ struct yes {}; \ struct no {}; \ \ struct ambiguate_seed { char mem; }; \ template < typename U > struct ambiguate : U, ambiguate_seed {}; \ \ template < typename U, typename = decltype(&U::mem) > static constexpr no test(int); \ template < typename > static constexpr yes test(...); \ \ static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ; \ typedef std::integral_constant<bool,value> type; \ }; #define HAS_MEM_FUN_CALL(memfun) \ \ template < typename Signature > \ struct has_valid_mem_fun_call_##memfun; \ \ template < typename T, typename... Args > \ struct has_valid_mem_fun_call_##memfun< T(Args...) > \ { \ struct yes {}; \ struct no {}; \ \ template < typename U, bool = has_mem_##memfun<U>::value > \ struct impl \ { \ template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \ struct test_result { using type = yes; }; \ \ template < typename V > static constexpr typename test_result<V>::type test(int); \ template < typename > static constexpr no test(...); \ \ static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value; \ using type = std::integral_constant<bool, value>; \ }; \ \ template < typename U > \ struct impl<U,false> : std::false_type {}; \ \ static constexpr bool value = impl<T>::value; \ using type = std::integral_constant<bool, value>; \ }; \ \ template < typename Signature > \ struct has_ambiguous_mem_fun_call_##memfun; \ \ template < typename T, typename... Args > \ struct has_ambiguous_mem_fun_call_##memfun< T(Args...) > \ { \ struct ambiguate_seed { void memfun(...); }; \ \ template < class U, bool = has_mem_##memfun<U>::value > \ struct ambiguate : U, ambiguate_seed \ { \ using ambiguate_seed::memfun; \ using U::memfun; \ }; \ \ template < class U > \ struct ambiguate<U,false> : ambiguate_seed {}; \ \ static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \ using type = std::integral_constant<bool, value>; \ }; \ \ template < typename Signature > \ struct has_viable_mem_fun_call_##memfun; \ \ template < typename T, typename... Args > \ struct has_viable_mem_fun_call_##memfun< T(Args...) > \ { \ static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value \ or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value; \ using type = std::integral_constant<bool, value>; \ }; \ \ template < typename Signature > \ struct has_no_viable_mem_fun_call_##memfun; \ \ template < typename T, typename... Args > \ struct has_no_viable_mem_fun_call_##memfun < T(Args...) > \ { \ static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value; \ using type = std::integral_constant<bool, value>; \ }; \ \ template < typename Signature > \ struct result_of_mem_fun_call_##memfun; \ \ template < typename T, typename... Args > \ struct result_of_mem_fun_call_##memfun< T(Args...) > \ { \ using type = decltype(std::declval<T>().memfun(std::declval<Args>()...)); \ }; #endif
requires
表达式 随着C ++ 20的出现,概念和各类工具(例如requires
表达式)成为检查函数是否存在的内置方式。 使用它们,您能够按以下方式重写您的optionalToString
函数:
template<class T> std::string optionalToString(T* obj) { constexpr bool has_toString = requires(const T& t) { t.toString(); }; if constexpr (has_toString) return obj->toString(); else return "toString not defined"; }
N4502提出了将其归入C ++ 17标准库的方法,能够用某种优雅的方式解决该问题。 并且,它刚刚被库基本原理TS v2接受。 它引入了一些元函数,包括std::is_detected
,可用于在其顶部轻松编写类型或函数检测元函数。 这是使用方法:
template<typename T> using toString_t = decltype( std::declval<T&>().toString() ); template<typename T> constexpr bool has_toString = std::is_detected_v<toString_t, T>;
请注意,上面的示例未经测试。 该检测工具包在标准库中尚不可用,可是该建议包含完整的实现,若是您确实须要,能够轻松复制该实现。 if constexpr
它能够与C ++ 17功能配合if constexpr
:
template<class T> std::string optionalToString(T* obj) { if constexpr (has_toString<T>) return obj->toString(); else return "toString not defined"; }
Boost.Hana显然基于此特定示例,并在其文档中提供了C ++ 14的解决方案,所以我将直接引用它:
[...] Hana提供了一个
is_valid
函数,能够将其与C ++ 14通用lambda结合使用,以更清晰地实现同一事物:auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });这给咱们留下了一个函数对象
has_toString
,该对象返回给定表达式在传递给它的参数上是否有效。 结果以IntegralConstant
形式返回,所以constexpr-ness在这里不是问题,由于不管如何该函数的结果都表示为类型。 如今,除了不那么冗长(这只是一个衬里!)以外,意图也更加清楚了。 其余好处是,has_toString
能够传递给更高阶的算法,也能够在函数范围内定义,所以无需使用实现细节来污染名称空间范围。
Boost.TTI是Boost 1.54.0中引入的另外一种惯用的工具包来执行这种检查,尽管它不那么优雅。 对于您的示例,您将必须使用宏BOOST_TTI_HAS_MEMBER_FUNCTION
。 这是使用方法:
#include <boost/tti/has_member_function.hpp> // Generate the metafunction BOOST_TTI_HAS_MEMBER_FUNCTION(toString) // Check whether T has a member function toString // which takes no parameter and returns a std::string constexpr bool foo = has_member_function_toString<T, std::string>::value;
而后,您可使用bool
建立SFINAE检查。
说明
宏BOOST_TTI_HAS_MEMBER_FUNCTION
生成元函数has_member_function_toString
,该函数将已检查的类型做为其第一个模板参数。 第二个模板参数与成员函数的返回类型相对应,如下参数与函数的参数类型相对应。 若是类T
具备成员函数std::string toString()
则成员value
包含true
。
另外, has_member_function_toString
能够将成员函数指针做为模板参数。 所以,能够用has_member_function_toString<std::string T::* ()>::value
替换has_member_function_toString<T, std::string>::value
has_member_function_toString<std::string T::* ()>::value
。
若是“若是我使用X,它将编译吗?”,这是C ++ 11的通用问题解决方案。
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
具备在此上下文中能够用0参数调用的.toString
方法时。
接下来,我将使用标签分配:
namespace details { template<class T> std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) { return obj->toString(); } template<class T> std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) { return "toString not defined"; } } template<class T> std::string optionalToString(T* obj) { return details::optionalToString_helper( obj, has_to_string<T>{} ); }
它比复杂的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
。 您为它传递所需特征的名称,以及一些能够测试类型T
代码。 从而:
MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )
建立上述特征类。
顺便说一句,以上技术是MS所谓的“表达式SFINAE”的一部分,而他们的2013编译器则至关失败。
请注意,在C ++ 1y中,如下语法是可能的:
template<class T> std::string optionalToString(T* obj) { return compiled_if< has_to_string >(*obj, [&](auto&& obj) { return obj.toString(); }) *compiled_else ([&]{ return "toString not defined"; }); }
这是一个内联编译条件分支,它滥用许多C ++功能。 这样作可能不值得,由于(代码内联)的好处不值得(几乎没人理解它的工做原理)的代价,可是上述解决方案的存在可能会引发人们的兴趣。