1. 程式人生 > >第19課 類型萃取(3)_類型選擇的traits

第19課 類型萃取(3)_類型選擇的traits

可用 class pac 利用 tro 功能 *** typename res

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

時,conditional::type被定義為T類型

  ②當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