第19課 類型萃取(3)_類型選擇的traits
1. std::conditional
(1)原型:template <bool Cond, class T, class F> struct conditional;
//根據條件獲取T或F類型 template<bool Cond, class T, class F> //泛化 struct conditional { typedef T type; }; template<class T, class F> //偏特化 struct conditional<false, T, F> { typedef F type; };
(2)說明:
①當cond為true
②當cond為false時,conditional::type被定義為F類型。
【編程實驗】std::conditional
#include <iostream> #include <typeinfo> using namespace std; int main() { typedef std::conditional<true, int, float>::type A; //int typedef std::conditional<false, int, float>::type B; //float typedef std::conditional<is_integral<A>::value, long, int>::type C; //long typedef std::conditional<is_integral<B>::value, long, int>::type D; //int //比較兩個類型,輸出較大的類型 typedef std::conditional<(sizeof(long long) > sizeof(long double)), long long, long double>::type max_size_t; cout << typeid(max_size_t).name() << endl; //long double return 0; }
2. std::enable_if
(1)enable_if的作用:
①當某個 condition 成立時,enable_if可以提供某種類型
②具備限定模板參數的作用,可以在編譯期檢查輸入的模板參數是否有效。
③可以用來控制重載函數是否可用,以實現強大的重載機制。
(2)std::enable_if的原型
①原型: template<bool cond, class T = void> struct enable_if;
//enable_if的可能實現 template<bool Cond, typename T = void> struct enable_if {}; //註意,沒有type類型 template<typename T> //偏特化,註意T的默認值為void struct enable_if<true, T> { typedef T type; };
②在 condition 為真的時候,由於偏特化機制,第2個結構體模板明顯是一個更好的匹配,所以 std::enable_if<>::type 就是有效的。
③當condition 為假的時候,只有第一個結構體模板能夠匹配,所以std::enable_if<>::type 是無效的,會被丟棄。同時,編譯器會報錯:error: no type named ‘type’ in ‘struct std::enable_if<false, bool>。
【編程實驗】利用std::enable_if檢查模板參數
#include <iostream> #include <typeinfo> using namespace std; //1. 模板參數只能是arithmetic(整型和浮點型) template<typename T> typename std::enable_if<is_arithmetic<T>::value, T>::type foo1(T t) { return t; } //2. 限定入參類型: template<typename T> //註意限制的是foo1的第2個形參,只能是整型 T foo2(T t, typename std::enable_if<std::is_integral<T>::value, int>::type = 0) { return t; } //3. 限定模板參數T的類型 (註意限制的是模板的T參數:為intergral類型)、 // 如果T是integral類型,is_integral<T>::value為false,enable_if<false>::type將報錯 template<typename T, class = typename std::enable_if<std::is_integral<T>::value>::type> T foo3(T t) { return t; } //4. 類模板特化時,參數的限定 //前向聲明,A為類模板 template<class T, class Enable = void> class A; template<class T> //模板特化 class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> //對模板參數的限定 {}; int main() { //1. auto r1 = foo1(1); //返回整數1 auto r2 = foo1(1.2); //返回浮點數1.2 //auto r3 = foo1("test"); //error //2. 限定入參類型 foo2(1, 2); //ok //foo2(1, ""); //error,第二個參數應為integral類型 //3.限定模板參數 foo3(1); //foo3(1.2); //error,模板參數的類型應為integral類型z z //4.類模板特化時,參數的限定 A<double> a1; //ok,先匹配特化模板,所以a的類型為A<double, double> //A<double, double> a2; //error, 顯式指式兩個參數。因此匹配的是第1個模板,但由於這裏只是聲明 //而未定義類(註意class A和class A{}的區別),所以會報A是個未完整類的錯誤。 //A<int> a3; //先匹配特化模板(失敗)。再匹配A<int, void>模板,但由於class A只是聲明,會與a2一樣。 //的錯誤。 return 0; }
(3)註意事項
①T的默認值為void類型,即enable_if的第2個模板參數不指定時,當cond為真,默認會獲取到的類型為void。
②當cond為假時,由於std::enable_if<>::type是無效的,因此編譯器會報錯。
【編程實驗】利用std::enable_if根據條件選擇重載函數
#include <iostream> using namespace std; //利用std::enable_if根據條件選擇重載函數 /********************************************************************************************/ //利用std::enable_if來選擇重載的模板函數foo //(註意,兩個模板函數僅返回值不同!而模板參數從形式上看雖然相同,但實參推導後T類型是不同的!) //1. 模板函數的參數相同,返回值不同函數的重載。(註意,實際推導後形參其實是不同的!) template <class T> typename std::enable_if<std::is_arithmetic<T>::value>::type //T為arithmetic類型時,返回值void foo(T& t) //兩個foo函數,模板參數相同。但實際推導後這裏是arithmetic類型。 { return; } template <class T> typename std::enable_if<std::is_class<T>::value, T>::type& //T為class時,T& foo(T& t) { return t; } //2. 模板函數的形參相同,返回值相同的函數重載。(註意,實際推導後形參其實是不同的!) //函數功能:將輸入的參數轉為string類型 //(對於arithemic類型調用std::to_string(t),對於string類型返回其本身) template<class T> typename std::enable_if<std::is_arithmetic<T>::value, string>::type //返回值string toString(T& t) { return std::to_string(t); } template<class T> typename std::enable_if<std::is_same<T, string>::value, string>::type //返回值 toString(T& t) { return t; } class Test{}; /********************************************************************************************/ //3. 可調用對象包裝器的實現 //3.1 無返回值的情況: template<class FT,class...Args> auto call(FT&& f, Args&&...args)-> //返回值為void typename std::enable_if<std::is_void<typename std::result_of<FT(Args...)>::type>::value, void>::type { f(std::forward<Args>(args)...); } //3.2 有返回值的情況 template<class FT, class...Args> auto call(FT&& f, Args&&...args)-> //當f有返回值時,則返回f原來的返回類型 typename std::enable_if<!std::is_void<typename std::result_of<FT(Args...)>::type>::value, typename std::result_of<FT(Args...)>::type>::type { return f(std::forward<Args>(args)...); } //3.3 萬能包裝器(統一以上兩種情況) template<typename FT, class...Args> auto FuncWrap(FT&& func, Args&& ...args)->decltype(func(std::forward<Args>(args)...)) { return func(std::forward<Args>(args)...); } int func(int a, int b) { cout << "int func(int a, int b):" << a + b << endl; return a + b; } int main() { //1. 選擇foo重載函數(返回值不同) int x = 1; foo(x); //匹配第1個模板,返回void類型 Test t; foo(t); //匹配第2個模板,返回Test& //2. 選擇toString重載函數(返回值相同) cout << toString(x) << endl; string s("abc"); cout << toString(s)<< endl; //3. 可調用對象包裝器 auto lda = [](){cout << "do anything you want!" << endl;}; call(lda); //無返回值 call([](int a){cout << "a = " << a << endl;}, 1); call(func, 1, 2); //帶返回值 FuncWrap(lda); //無返回值 FuncWrap(func, 1, 2); //帶返回值 return 0; } /*輸出結果 e:\Study\C++11\19>g++ -std=c++11 test3.cpp e:\Study\C++11\19>a.exe 1 abc do anything you want! a = 1 int func(int a, int b):3 do anything you want! int func(int a, int b):3 */
第19課 類型萃取(3)_類型選擇的traits