可變參數模板
原文鏈接: http://blog.csdn.net/xiaohu2022/article/details/69076281
普通模板只可以采取固定數量的模板參數。然而,有時候我們希望模板可以接收任意數量的模板參數,這個時候可以采用可變參數模板。對於可變參數模板,其將包含至少一個模板參數包,模板參數包是可以接收0個或者多個參數的模板參數。相應地,存在函數參數包,意味着這個函數參數可以接收任意數量的參數。
使用規則
一個可變參數類模板定義如下:
template<typename ... Types> class Tuple {};
可以用任意數量的類型來實例化Tuple:
Tuple<> t0; Tuple<int> t1; Tuple<int, string> t2; // Tuple<0> error; 0 is not a type
如果想避免出現用0個模板參數來實例化可變參數模板,可以這樣定義模板:
template<typename T, typename ... Types> class Tuple {};
此時在實例化時,必須傳入至少一個模板參數,否則無法編譯。
同樣地,可以定義接收任意參數的可變參數函數模板:
template<typename ... Types> void f(Types ... args); cout<<sizeof...(args)<<endl;//打印可變參數的個數 // 一些合法的調用 f(); f(1); f(3.4, "hello");
對於類模板來說,可變模板參數包必須是模板參數列表中的最后一個參數(即模板列表參數中的最后一個參數必須是可變的)。但是對於函數模板來說,則沒有這個限制,考慮下面的情況:
template<typename ... Ts, typename U> class Invalid {}; // 這是非法的定義,因為永遠無法推斷出U的類型 template<typename ... Ts, typename U> void valid(U u, Ts ... args); // 這是合法的,因為可以推斷出U的類型 // void invalid(Ts ... args, U u); // 非法的,永遠無法推斷出U valid(1.0, 1, 2, 3); // 此時,U的類型是double,Ts是{int, int, int}
可變參數函數模板實例
無法直接遍歷傳給可變參數模板的不同參數,但是可以借助遞歸的方式來使用可變參數模板。可變參數模板允許創建類型安全的可變長度參數列表。下面定義一個可變參數函數模板processValues(),它允許以類型安全的方式接受不同類型的可變數目的參數。函數processValues()會處理可變參數列表中的每個值,對每個參數執行對應版本的handleValue()。
// 處理每個類型的實際函數 void handleValue(int value) { cout << "Integer: " << value << endl; } void handleValue(double value) { cout << "Double: " << value << endl; } void handleValue(string value) { cout << "String: " << value << endl; } // 用於終止迭代的基函數 template<typename T> void processValues(T arg) { handleValue(arg); } // 可變參數函數模板 template<typename T, typename ... Ts> void processValues(T arg, Ts ... args) { handleValue(arg); processValues(args ...); // 解包,然后遞歸 }
可以看到這個例子用了三次... 運算符,但是有兩層不同的含義。用在參數模板列表以及函數參數列表,其表示的是參數包。前面說到,參數包可以接受任意數量的參數。用在函數實際調用中的...運算符,它表示參數包擴展,此時會對args解包,展開各個參數,並用逗號分隔。模板總是至少需要一個參數,通過args...解包可以遞歸調用processValues(),這樣每次調用都會至少用到一個模板參數。對於遞歸來說,需要終止條件,當解包后的參數只有一個時,調用接收一個參數模板的processValues()函數,從而終止整個遞歸。
假如對processValues()進行如下調用:
processsValues(1, 2.5, "test");
其產生的遞歸調用如下:
processsValues(1, 2.5, "test"); handleValue(1); processsValues(2.5, "test"); handleValue(2.5); processsValues("test"); handleValue("test");
#include <iostream> #include<string> #include<string.h> #include <memory> using namespace std; // 用來終止遞歸並處理包中最后一個元素 template <typename T> void print(T t) { cout << t; } // 包中除了最后一個元素之外的其他元素都會調用這個版本的print template <typename T, typename...Types> void print(T t, Types ...Arg) { cout << t << endl; // 打印第一個實參 print(Arg...); // 遞歸調用,打印其他實參 } // 測試 int main() { print("string1", 2, 3.14f, "string2", 42); cout << endl; system("pause"); return 0; }
類模板的全特化:就是模板中的參數類型都確定了
類模板的偏特化:就是模板中有些參數確定了,有些沒有確定
注意:模板的全特化和偏特化都要在類模板的基礎上實現
//先定義類模板 template<typename T1, typename T2> class Test { public: Test(T1 i, T2 j) :a(i), b(j) { cout << "模板類" << endl; } private: T1 a; T2 b; }; //模板的全特化 template<> class Test<int, char> { public: Test(int i, char j) :a(i), b(j) { cout << "全特化" << endl; } private: int a; char b; }; //模板的偏特化 template <typename T2> class Test<char, T2> { public: Test(char i, T2 j) :a(i), b(j) { cout << "偏特化" << endl; } private: char a; T2 b; }; int main() { string s; //調用類模板 Test<int, int> A(1, 1); //模板的全特化 Test<int, char> B(1, 'c'); //模板的偏特化 Test<char, int> C('c', 1); system("pause"); return 0; }
