C++ 11中引入的tuple是一個N元組。它相當於有N個成員的結構體,只不過這個結構體的成員都是匿名的。tuple中有兩個特殊的函數,一個是head(),用於擷取第一個成員的值,另一個是tail(),用於擷取剩下所有成員的值,tail()本身又是一個tuple。這樣,如果我們想取tuple中第二個成員的值,則可以先取tail()的值,再取tail()的head()的值。當然,這樣使用的話比較麻煩,所以C++ 11提供了get函數通過索引來擷取tuple中某個成員的值。另外,通過make_tuple可以很方便地構造一個tuple對象。有關tuple使用的例子可以參考下面的代碼。
1 tuple<int, char, string> tupInfo(10, 'A', "hello world");
2 int a = tupInfo.head();
3 int a2 = tupInfo.tail().head();
4 tuple<char, string> tupTail = tupInfo.tail();
5 int b = get<0>(tupInfo);
6 char c = get<1>(tupInfo);
7 string s = get<2>(tupInfo);
8 auto tupInfo2 = make_tuple(5, 'B', string("C++ 11"), 4.6);
前面說過,tuple是一個N元組,而N的個數是沒有限制的,也就是說,tuple可以包含0個、1個、2個或更多的元素,每個元素的類型則通過模板參數指定。那麼,tuple是如何做到這些的呢?答案是可變參數模板。
學習C++的人應當對printf函數都非常熟悉,printf的一個特點就是它的參數個數是可變的。而在C++ 11中,則允許模板的參數個數也是可變的。下面是一個模板參數可變的函數模板,用於擷取傳入的參數的個數。
1 template<typename... Args>
2 UINT GetParameterCount(Args... args)
3 {
4 return sizeof...(args);
5 }
可以看到,可變參數模板使用typename再加...來表示模板參數包,使用Args再加...來表示函數參數包。上面代碼中的sizeof...專門用於擷取函數參數包中參數的個數,它的參數必須是一個函數參數包類型的對象。熟悉了可變參數模板的基本文法後,下面我們使用它來編寫一個Print函數,該函數的參數個數和類型都是可變的,它簡單地輸出傳入的各個參數的值,值之間用逗號進行分割,並在輸出最後一個參數的值後自動換行。
1 template<typename T>
2 void Print(T value)
3 {
4 cout << value << endl;
5 }
6
7 template<typename Head, typename... Rail>
8 void Print(Head head, Rail... rail)
9 {
10 cout << head << ",";
11 Print(rail...);
12 }
13
14 int main(int argc, char *argv[])
15 {
16 Print(1); // 輸出:1
17 Print(1, "hello"); // 輸出:1,Hello
18 Print(1, "hello", 'H'); // 輸出:1,Hello,H
19 return 0;
20 }
在上面的代碼中,我們先定義了一個只有一個模板參數的函數模板,它簡單地輸出傳入的參數的值。然後又定義了一個可變參數的函數模板,它輸出第一個參數的值,然後遞迴地調用自己。注意rail...這種寫法,它表示將函數參數包分割成一個一個的參數,並傳入Print中。這樣,函數參數包中的第一個參數傳遞給head,剩餘的參數又重新構成一個函數參數包傳遞給rail。當遞迴調用到函數參數包中只有一個參數時,則會調用只有一個模板參數的Print函數。
理解了可變模板參數的使用原理後,我們再來編寫一個自己的Printf函數。
1 void MyPrintf(const char *pszText)
2 {
3 assert(pszText != NULL);
4
5 cout << pszText;
6 }
7
8 template<typename T, typename... Args>
9 void MyPrintf(const char *pszText, T value, Args... args)
10 {
11 assert(pszText != NULL);
12
13 while (*pszText)
14 {
15 if (*pszText == '%' && *++pszText != '%')
16 {
17 cout << value;
18 MyPrintf(++pszText, args...);
19 return;
20 }
21 cout << *pszText++;
22 }
23 }
調用MyPrintf函數時的推理過程與Print的推理過程類似,這裡就不再贅述了。另外,如果想更深入地學習可變參數模板,還可以參閱tuple的原始碼,或者自己動手實現一個簡化版的tuple。
介紹C++ 11新特性的文章列表:
1、【原】C++ 11右值引用
2、【原】C++ 11 Lambda運算式
3、【原】C++ 11 auto & decltype
4、【原】C++ 11完美轉寄
5、【原】C++ 11文法甜點
6、【原】C++ 11 tuple & 可變參數模板