是否能夠編寫一個模板來更改行爲,具體取決因而否在類上定義了某個成員函數? 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 ++功能。 這樣作可能不值得,由於(代碼內聯)的好處不值得(幾乎沒人理解它的工做原理)的代價,可是上述解決方案的存在可能會引發人們的興趣。