在type_traits頭文件中定義了許多非常好玩的東西,這里對着 c++ reference 翻譯一丟丟
一,helper class , std::intergral_constant
template< class T, T v > struct integral_constant;
我們知道對在模板參數中的非類型參數必須為常量,所以這個東西就是可以為類型T的任意一個常量v,做出來一個特定的類型,即integral_constant<T, v>的實例。通常用來保存int 和 bool,它的實現是這樣的:
//聲明一個為bool偏特化的模板
template <bool B>
using bool_constant = integral_constant<bool, B>;
//這個偏特化的模板總共就能實例化出兩個具體的類型:
typedef std::integral_constant<bool, true> true_type typedef std::integral_constant<bool, false> false_type //可能的實現 template<class T, T v> struct integral_constant { static constexpr T value = v; typedef T value_type; typedef integral_constant type; constexpr operator value_type() const noexcept { return value; } constexpr value_type operator()() const noexcept { return value; } //since c++14 };
那么,在模板編程中,計算都是在編譯期搞定的, 作為模板參數的非類型參數必須為常量, 這也就說模板元編程中的變量一定是常量。。。如果要改變值只能在搞一個新的常量咯,
這個intergral_constant就能枚舉完 T 中的任何一個常量。
二、主要的類型分類,以下的每個類型都有一個value成員,如果判斷為真,則為true, 否則為false,這里挑幾個簡單介紹:
1, is_void, 用法 is_void<T>::value, 判斷T是否為void類型, 一看就是用來判斷函數返回值類型的
2, is_same<T1,T2> , 用法 is_same<T1, T2>::value, 判斷兩個類型是否一致,配合typedecl簡直不要太爽。。。不過得注意typedecl的引用
。。。。。剩下的略
三、enable_if
原型:
template< bool B, class T = void > struct enable_if;
用法, 如果B為true, 則 enable_if<B, T>會有一個類型成員,名為type, 類型為 T, 配合上面的各種判斷,以及SFINAE 特性(無法實例化並不是錯誤),可以實現花式的自定義重載函數集:
// 1. the return type (bool) is only valid if T is an integral type: template <class T> typename std::enable_if<std::is_integral<T>::value,bool>::type is_odd (T i) {return bool(i%2);} // 2. the second template argument is only valid if T is an integral type: template < class T, class = typename std::enable_if<std::is_integral<T>::value>::type> // 這么寫應該更容易明白: class T2 = typename std::enable_if<std::is_integral<T>::value>::type> bool is_even (T i) {return !bool(i%2);} int main() { short int i = 1; // code does not compile if type of i is not integral std::cout << std::boolalpha; std::cout << "i is odd: " << is_odd(i) << std::endl; std::cout << "i is even: " << is_even(i) << std::endl; return 0; }
或者更秀操作的這樣:
#include <type_traits> #include <iostream> #include <string> namespace detail { struct inplace_t{}; } void* operator new(std::size_t, void* p, detail::inplace_t) { return p; } // #1, enabled via the return type template<class T,class... Args> typename std::enable_if<std::is_trivially_constructible<T,Args&&...>::value>::type construct(T* t,Args&&... args) { std::cout << "constructing trivially constructible T\n"; } // #2 template<class T, class... Args> std::enable_if_t<!std::is_trivially_constructible<T,Args&&...>::value> //Using helper type construct(T* t,Args&&... args) { std::cout << "constructing non-trivially constructible T\n"; new(t, detail::inplace_t{}) T(args...); } // #3, enabled via a parameter template<class T> void destroy(T* t, typename std::enable_if<std::is_trivially_destructible<T>::value>::type* = 0) { std::cout << "destroying trivially destructible T\n"; } // #4, enabled via a template parameter template<class T, typename std::enable_if< !std::is_trivially_destructible<T>{} && (std::is_class<T>{} || std::is_union<T>{}) >::type* = nullptr> void destroy(T* t) { std::cout << "destroying non-trivially destructible T\n"; t->~T(); } // #5, enabled via a template parameter template<class T, typename = std::enable_if_t<std::is_array<T>::value> > void destroy(T* t) // note, function signature is unmodified { for(std::size_t i = 0; i < std::extent<T>::value; ++i) { destroy((*t)[i]); } } /* template<class T, typename = std::enable_if_t<std::is_void<T>::value> > void destroy(T* t){} // error: has the same signature with #5 */ // the partial specialization of A is enabled via a template parameter template<class T, class Enable = void> class A {}; // primary template template<class T> class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> { }; // specialization for floating point types int main() { std::aligned_union_t<0,int,std::string> u; construct(reinterpret_cast<int*>(&u)); destroy(reinterpret_cast<int*>(&u)); construct(reinterpret_cast<std::string*>(&u),"Hello"); destroy(reinterpret_cast<std::string*>(&u)); A<int> a1; // OK, matches the primary template A<double> a2; // OK, matches the partial specialization }
上面的代碼中,有一些會根據不同的條件來改變函數的簽名,而有的則不會, 具體使用哪種應該看情況使用,比如調整重載候選函數集合時應該主動改變函數簽名,而調整虛函數時則不應該改變函數簽名