1. 程式人生 > >第21課 可變參數模板(2)_展開參數包

第21課 可變參數模板(2)_展開參數包

delet pre 控制 seq src 構造 pro head del

1. 可變參數模板函數

(1)遞歸函數方式展開參數包

  ①一般需要提供前向聲明、一個參數包的展開函數和一個遞歸終止函數。

  ②前向聲明有時可省略,遞歸終止函數可以是0個或n個參數

(2)逗號表達式和初始化列表方式展開參數包

  ①逗號表達式按順序執行,返回最後一個表達式的值。

  ②initilizer_list可接受任意多個不同類型的參數。

  ③借助逗號表達式來展開包,並將返回的結果用於初始化initilizer_list。

【編程實驗】展開可變參數模板函數的參數包

#include <iostream>
#include <tuple>
//#include <stdexcept>
using namespace std; /************利用遞歸方式展開可變參數模板函數的參數包************/ //1.求最大值(可接受多個參數) int maximum(int n) //遞歸終止函數 { return n; } template<typename... Args> int maximum(int n, Args... args) //遞歸函數 { return std::max(n, maximum(args...)); } //2. 用可變參數模板函數模仿printf的功能 void Printf(const char* s) //
遞歸終止函數 { while(*s){ if(*s == % && *(++s)!=%) throw std::runtime_error("invalid format string: missing arguments"); cout << *s++; } } template<typename T, typename... Args> void Printf(const char* s, T value, Args...args) //遞歸函數,展開參數包(value + args...)
{ while(*s){ if(*s == % && *(++s)!=%){ //找到格式控制符% cout << value; Printf(++s, args...); //call even when *s=0 to detect extra argument return; } cout << *s++; //"%d %s %p %f\n"輸出非格式控制字符,如空格。 } throw runtime_error("extra arguments provided to print"); } //3. 使用tuple轉化並分解參數包 template<std::size_t Index=0, typename Tuple> //遞歸終止函數 typename std::enable_if<Index==std::tuple_size<Tuple>::value>::type //返回值void類型 print_tp(Tuple&& t) //tp: tuple { } template<std::size_t Index=0, typename Tuple> //遞歸終止函數 typename std::enable_if<Index<std::tuple_size<Tuple>::value>::type //返回值void類型 print_tp(Tuple&& t) //tp: tuple { cout << std::get<Index>(t) << endl; print_tp<Index+1>(std::forward<Tuple>(t)); } template<typename... Args> void print(Args... args) { print_tp(std::make_tuple(args...)); //將args轉為tuple } /************利用逗號表達式和初始化列表展開可變參數模板函數的參數包************/ template<typename T> void printargs(T t) //註意這不是遞歸終止函數!而是一個用來輸出參數內容的函數 { cout << t << endl; }; template<class... Args> void expand(Args... args) { //方法1:數組的初始化列表 //int arr[] = {(printargs(args),0)...};//逗號表達式。包擴展為(printargs(args1),0) //(printargs(args1),0),...,(printargs(argsN),0) //計算每個逗號表達式,調用printargs()(在這裏獲得各個參數) //同時,每個逗號表達式結果得0,然後用N個0初始化arr。 //方法2:利用std::initializer_list //std::initializer_list<int>{(printargs(args),0)...}; //比方法1簡潔,且無需定義一個輔助的arr。 //方法3:利用lambda表達式 //[&args...]{std::initializer_list<int>{(cout << args << endl,0)...};}(); [&]{std::initializer_list<int>{(cout << args << endl,0)...};}(); } int main() { /*******************遞歸方式展開參數包******************************/ //1. 求最大值 cout << maximum(57, 48, 60, 100, 20, 18) << endl; //2. 模擬printf的功能 int* pi = new int; Printf("%d %s %p %f\n", 15, "This is Ace.", pi, 3.1415926); //15 This is Ace. 0x6a77f0 3.14159 delete pi; //3.利用tuple分解參數包 print(15, "This is Ace.", pi, 3.1415926); cout << endl; /*******************逗號表達式方式展開參數包******************************/ expand(15, "This is Ace.", pi, 3.1415926); expand(57, 48, 60, 100, 20, 18); return 0; }

2. 可變參數模板類

(1)模板遞歸特化方式展開參數包

  ①一般需要類聲明、類定義和特化模板類

  ②特化模板類用於遞歸的終止

(2)繼承(或組合)方式展開參數包

  ①一般需要類聲明、類定義和特化模板類

  ②特化模板類用於遞歸的終止(可以是參數個數減少,或滿足一個條件停止遞歸)

  ③模板類繼承是遞歸定義的,直到父模板類的模板參數滿足遞歸終止條件,如下圖。也可以采用組合的方式進行遞歸。

技術分享 技術分享

【編程實驗】展開可變參數模板類的參數包

#include <iostream>
using namespace std;

//1. 遞歸方式展開參數包
template<typename ...Args> struct Sum; //類模反的前向聲明

template<typename First, typename ...Rest>  //類模板的定義
struct Sum<First, Rest...>
{
    enum{value = Sum<First>::value + Sum<Rest...>::value};
};

template<typename Last>  //遞歸終止類
struct Sum<Last>
{
    enum{value = sizeof(Last)}; //求出Last類的大小
};

//2. 繼承方式展開參數包
template<typename ...Values> class Tuple; //前向聲明

template<typename Head, typename ...Tail>
class Tuple<Head, Tail...> : private Tuple<Tail...>  //私有繼承自tuple<Tail...> 
{
    typedef Tuple<Tail...> inherited; //父類    
protected:
    Head m_head;

public:
    Tuple(){}
    //構造函數
    Tuple(Head v, Tail...vtail): m_head(v),inherited(vtail...) //inherited(...)不是創建臨時對象
    {                                                          //而是調用父類構造函數來初始化!
        
    }
    
    //獲取head和tail
    Head head(){return m_head;} //獲取head
    inherited& tail(){return *this;} //c++對象內存模型,this指向對象開始的
                                     //地址,也是父類開始的地方。
};

template<> class Tuple<>{}; //遞歸終止類

//3. 組合方式展開參數包
template<typename ...Values> class tup; //前向聲明

template<typename Head, typename ...Tail>
class tup<Head, Tail...>
{
    typedef tup<Tail...> composited;
protected:
    composited m_tail;  //組合方式
    Head m_head;
public:
    tup(){}
    
    tup(Head v, Tail...vtail):m_head(v), m_tail(vtail...)
    {
        
    }
    
    Head head() {return m_head;}
    composited& tail(){return m_tail;}
};

template<>class tup<>{}; //遞歸終止類

int main()
{
    //1. 模板遞歸
    cout <<Sum<char, double, float, int>::value << endl; //17
    
    //2. 繼承方式
    Tuple<int, float, string> t(41, 6.3, "nico");
    cout << t.head() << endl;        //41
    cout << t.tail().head() << endl; //6.3
    cout << t.tail().tail().head() << endl; //"nico"
    cout <<"&t:"<< &t <<", &t.tail:" <<&t.tail() << endl;
    
    //3.組合方式
    tup<int, float, string> tc(41, 6.3, "nico");
    cout << tc.head() << endl;        //41
    cout << tc.tail().head() << endl; //6.3
    cout << tc.tail().tail().head() << endl; //"nico"

    return 0;
}

3.可變參數模板的妙用

(1)可變參數模板的一個特性就是參數包中的模板參數可以是任意數量和任意類型。因此可以以一種更加泛化的方式去處理一些問題,從而消除重復的代碼。

(2)可變參數模板實現泛化的delegate

(3)可變模版參數(variadic templates)是C++11新增的最強大的特性之一,也是C++11中最難理解和掌握的特性之一。它可變模版參數比較抽象,使用起來需要一定的技巧。

(4)可變模板參數不能直接作為變量保存起來,需要借助tuple保存起來。

【編程實驗】可變參數模板的妙用

#include <iostream>
#include <tuple>
#include <typeinfo>
using namespace std;

class Test
{
    int a;
    int b;
    int c;
public:
    Test():a(0),b(0),c(0)
    {
        cout << "Test()"<< endl;
    }
    
    Test(int a, int b):a(a),b(b),c(0)
    {
        cout << "Test(int a, int b)"<< endl;
    }
    
    Test(int a, int b, int c):a(a),b(b),c(c)
    {
        cout << "Test(int a, int b, int c)"<< endl;
    }
    
    void add(int x, int y)
    {
        cout << "x + y = "<< x + y << endl;
    }
    
        void sub(int x, int y)
    {
        cout << "x - y = "<< x - y << endl;
    }
};

/*********************************************************************************************/
//1. 創建對象的工廠函數
//(可以消除因T帶有多個不同參數的構造函數而導致需重載多個createInstance的問題。
template<class T, typename ...Args>
T* createInstance(Args&& ...args)
{
    return new T(std::forward<Args>(args)...);
}

/*********************************************************************************************/
//2. 利用可變參數模板實現類似C#中的委托功能
template<class T, class R, typename...Args>
class Delegate
{
private:
    T* m_t; //被委托對象
    R (T::*m_f)(Args...); //被委托對象的成員函數指針
public:
    Delegate(T* t, R (T::*f)(Args...)): m_t(t), m_f(f){}
    
    R operator()(Args&&... args)
    {
        return (m_t->*m_f)(std::forward<Args>(args)...);
    }
};

//創建委托對象
template<class T, class R, typename...Args>
Delegate<T, R, Args...>  //返回值
createDelegate(T* t, R (T::*f)(Args...))
{
    return Delegate<T, R, Args...>(t, f);
}

/*********************************************************************************************/
//3. 創建整數序列(如IndexSeq<0,1,2,3,4,5>)
template<int...>
struct IndexSeq{};  //定義整數序列

//繼承方式展開參數包
//說明:
//(1)也可以不用繼承。采用using來實現,如using type = MakeIndexs<N-1, N-1, Indexs...>::type)
//(2)MakeIndexs<N-1, Indexs..., N-1>:第1個為N-1遞歸終止條件,第2個N-1表示將其放入參數包indexs的最後面,
//     即每次更小的數會被放入參數包的最後面,所以呈降序。
//(3)MakeIndexs<N-1, N-1, Indexs...> ,第1個N-1表示遞歸終止條件,第2個N-1將其放入參數包indexs的前面,
//     即更小的數會被放入參數包的前面。因此,呈升序序列。
template<int N, int... Indexs> 
struct MakeIndexs : MakeIndexs<N-1, N-1, Indexs...>{}; //private繼承,升序序列
//struct MakeIndexs : MakeIndexs<N-1, Indexs..., N-1>{}; //降序序列

//特化模板:終止遞歸
template<int...Indexs>
struct MakeIndexs<0, Indexs...>
{
    typedef IndexSeq<Indexs...> type; 
};

/*********************************************************************************************/
//4. 利用整數序列輸出可變模板參數的內容
//tuple在模版元編程中的一個應用場景是將可變模板參數保存起來,
//因為可變模板參數不能直接作為變量保存起來,需要借助tuple保存起來,
//保存之後再在需要的時候通過一些手段將tuple又轉換為可變模板參數,
//這個過程有點類似於化學中的“氧化還原反應”。

template <typename T>
void print(T t)
{
    cout << t << endl;
}

template <typename T, typename...Args>
void print(T t, Args... args)
{
    print(t);
    print(args...);
}

template<int... Indexs, typename ...Args> //將Args...轉為tuple,並生成整數序列。
void print_helper(IndexSeq<Indexs...>, std::tuple<Args...>&& tup)
{
    print(std::get<Indexs>(tup)...); //打印這個tuple
                                     //展開後,得get<0>(tup),...,get<N-1>(tup)
                                     //將tup的內容還原成一個可變參數,傳到print去輸出
}

//先將可變模板參數保存到tuple中,然後再通過元函數MakeIndexes生成一個整形序列,
//這個整形序列就是IndexSeq<0,1,2>,整形序列代表了tuple中元素的索引。
//再調用print_helper,展開這個整形序列,並根據具體的索引從tuple中獲取對應的元素
//最終將從tuple中取出來的元素組成一個可變模板參數,從而實現了tuple“還原”為可變模板參數,
//最終調用print打印可變模板參數
template<typename ...Args>
void printargs(Args&&... args)
{
    print_helper(typename MakeIndexs<sizeof...(args)>::type(), std::make_tuple(args...));
}

int main()
{
    //創建對象的工廠函數
    Test* p1 = createInstance<Test>();
    Test* p2 = createInstance<Test>(1, 2);
    Test* p3 = createInstance<Test>(1, 2, 3);
    
    //利用可變參數模板實現類似C#中的委托功能
    Test t;
    auto d1 = createDelegate(&t, &Test::add); //創建委托
    d1(1,2);  //調用委托
    
    auto d2 = createDelegate(&t, &Test::sub);
    d2(5,3);
    
    //創建整數序列
    using T = MakeIndexs<3>::type;
    cout << typeid(T).name() << endl;
    
    //利用整數序列輸出可變模板參數的內容
    print(1, 2.5, "test");   
    printargs(1, 2.5, "test"); //與print的區別:printargs會將可變參數保存起來,而
                               //前者不會。
    return 0;
}

第21課 可變參數模板(2)_展開參數包