c++11——可變參數模板


    在c++11之前,類模板和函數模板只能含有固定數量的模板參數,c++11增加了可變模板參數特性:允許模板定義中包含0到任意個模板參數。聲明可變參數模板時,需要在typename或class后面加上省略號"..."。 
    省略號的作用有兩個: 
1. 聲明一個參數包,這個參數包中可以包含0到任意個模板參數 
2. 在模板定義的右邊,可以將參數包展開成一個一個獨立的參數

1. 可變參數模板函數

    可變參數模板函數的定義如下:

template<class... T>
void f(T... args){
    cout << sizeof...(args) << endl; //sizeof...(args) 取得可變參數的個數
}
f();
f(1, 2);
f(1, 2.3, "hello");

 

    參數包可以包含0個或者多個參數,如果需要用參數包中的參數,則一定要將參數包展開。有兩種展開參數包的方法:(1)通過遞歸的模板函數來展開參數包;(2)通過逗號表達式和初始化列表方式展開參數包。

展開參數包 
(1)遞歸函數方式展開參數包 
    需要提供一個參數包展開的函數和一個遞歸終止函數,二者同名。遞歸終止函數的參數可以為0,1,2或者多個(一般用到0個或1個),當參數包中剩余的參數個數等於遞歸終止函數的參數個數時,就調用遞歸終止函數,則函數終止。

#include<iostream>
using namespace std;
//遞歸終止函數
void print(){
    cout << "empty" << endl;
}
//展開函數
template<class T, class... Args>
void print(T head, Args... rest){
    cout << "parameter = " << head << endl;
    print(rest...);
}

int main(){
    print(1,2,3,4);
    return 0;
}
//當調用print(1,2,3,4)時,先后調用print(2,3,4), print(3,4),print(4),最終調用print()終止。
如果遞歸終止函數為
template<typename T>
void print(T a){
    cout << a << endl;
}則函數調用到 print(4)就終止。

 

還可以通過type_traits方式來定義同名但不同參數的函數,分別實現遞歸終止和展開函數,從而展開參數包

//相當於遞歸終止函數
template<typename I = 0, typename Tuple>
typename std::enable_if<I==std::tuple_size<Tuple>::value>::type printtp(Tuple t){
    
}
//相當於展開函數
template<typename I = 0, typename Tuple>
typename std::enable_if<I < std::tuple_size<Tuple>::value>::type printtp(Tuple t){
    std::cout << std::get<I>(t) << std::endl;   //打印出元組中的第i個
    printtp<I+1>(t);
}

template<typename... Args>
void print(Args... args){
    printtp(std::make_tuple(args...);
}

 

(2)初始化列表方式展開參數包 
    遞歸函數展開參數包,需要有一個同名的終止函數來終止遞歸,可以使用初始化列表的方式避免多定義一個同名的終止函數。

template<typename T>
void printarg(T a){
    cout << a << endl;
}

template<class...Args>
void expand(Args... args){
    int arr[] = {(printarg(args), 0)...};
    //或者改進為 std::initializer_list<int>{(printarg(args), 0)...};
}
//{(printarg(args), 0)...}會被展開為{(printarg(arg1), 0), (printarg(arg2), 0), (printarg(arg3), 0)}
expand(1,2,3,"hello");

 


    這種展開參數包的方式,不需要通過遞歸終止函數,而是直接在expand函數體內展開,printarg不是一個遞歸終止函數,只是一個處理參數包中每一個參數的函數。這種處理方式的關鍵是逗號表達式。

    逗號表達式會按順序執行前面的表達式,比如d = (a = b, c);b先賦值給a,接着括號中的逗號表達式返回c的值,因此d被賦值為c 
    c++11中使用列表初始化方法來初始化一個邊長的數組,可以使用 int arr[] = {args...};其中args為一個變長的參數集合。

template<typename... Args>
void print(Args... args){
    int arr[] = { (args, 0)... }; //具名的args...(或者匿名的...) 代表所有的可變參數集合,可以將args和...分開,此時args表示...中每一個參數。
}

 

    還可以通過lambda表達式來改進上述的列表初始化方式:

template<typename... Args>
void expand(Args... args){
    std::initializer_list<int>{([&]{cout << args << endl;}(), 0)...};
}

 

2. 可變參數模板類

    tuple是一個可變參數模板類:

    template<class... Types>
    class tuple;
    這個可變參數模板類可以攜帶任意類型任意個數的模板參數
    std::tuple<int> tp1 = std::make_tuple(1);
    std::tuple<int, double> tp2 = std::make_tuple(1,2.4);
    std::tuple<> tp;

 

可變參數模板類的參數展開
(1)模板遞歸和特化方式展開參數包

    可變參數模板類的展開一般需要2~3個類,包括類聲明和特化的模板類。如下方式定義了一個基本的可變參數模板類:

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> //遞歸終止類,模板參數不一定為1個,可能為0個或者2個
struct Sum<Last>{
    enum{value = sizeof(Last)};
};
這個Sum類的作用是在編譯期計算出參數包中參數類型的size之和。

或者可通過std::integral_constant來修改一下:
template<typename... Args>      //前置聲明
struct Sum;

template<typename First, typename... Rest>  //遞歸定義
struct Sum<First, Rest...>: std::integral_constant<int, std::integral_constant<int, sizeof(First)>::value + Sum<Rest...>::value{
};

template<typename Last> //遞歸終止
struct Sum<Last>: std::integral_constant<int, sizeof(Last)>{
};

 


(2)繼承方式展開參數包
//整型序列的定義
template<int...>
struct IndexSeq{};

//繼承方式,開始展開參數包
template<int N, int ... Indexes>
struct MakeIndexes: MakeIndexes<N -1, N -1, Indexes...>{};

//模板特化,終止展開參數包的條件
template<int... Indexes>
struct MakeIndexes<0, Indexes...>{
    typedef IndexSeq<Indexes...> type;
};
int main(){
    using T = MakeIndexes<3>::type;  //輸出為 struct IndexSeq<0,1,2>
    cout << typeid(T).name() << endl;
    return 0;
};

//MakeIndexes如果不通過繼承遞歸方式生成,可以通過using來實現。
template<int N, int ...Indexes>
struct MakeIndexes{
    using type = MakeIndexes<N-1,N-1,Indexes>::type;
};

template<int...Indexes>
struct MakeIndexes<0, ...Indexes>{
    using type =  IndexSeq<Indexes...>;
};

 

可以使用上述的IndexSeq來展開並打印可變模板參數,比如:
template<int ...Indexes, typename ...Args>
void print_helper(IndexSeq<Indexes...>, std::tuple<Args...>&& tup){
    print(std::get<Indexes>(tup)...);
}

template<typename ...Args>
void print(Args... args){
    print_helper(typename MakeIndexes<sizeof...(args)>::type(), std::make_tuple(args...));
}

 




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM