C++11新特性之 可變參數模板

C++ 11的可變模版參數是其新增的最強大的特性之一。通過對參數進行了泛化,可以表示從0到任意個數、任意類型的參數。我們知道對於一個模板類來說,通常只能含固定數量的模版參數,可變模版參數無疑是一個巨大的改進。下面來具體說一下:

可變參數模板與通常的類模板寫法相似。聲明可變參數模板時需要在typename後面帶上省略號 ... 。寫法如下:

template<typename T, typename ...Types>
T mul(T head, Types ...rest){
	return head * mul<T>(rest...);
}

省略號當放在聲明Types左邊,表示一個參數包聲明,這個參數包聲明可以包括0以上任意多個參數;放在右邊,如rest...則表示實際參數包,可以將其展開成一個一個獨立的參數。我們無法直接獲取參數包types中的每個具體參數的,只能通過展開參數包的方式來獲取參數包中的每個參數,這是使用可變模版參數的一個主要特點,也是最大的難點,即如何展開可變模版參數。

首先來看一個最簡單的可變模板參數:

#include <iostream>
#include <string>
#include <queue>
#include <vector>
#include <map>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <functional>
using namespace std;

template <class... Types>
void f(Types... args)
{
	cout << sizeof...(args) << endl; //輸出可變參數的總數量
}

int main()
{
	f();
	f("aaa");
	f(1, 2.0);
	system("pause");
	return 0;
}

顯然輸出爲0,1,2.  因爲主函數中,f()沒有傳入參數,所以參數包爲空,輸出的size爲0,後面兩次調用分別傳入1個和2個參數,故輸出的size分別爲1和2。由於可變模版參數的類型和個數是不固定的,所以我們可以傳任意類型和個數的參數給函數f。這個例子只是簡單的將可變模版參數的個數打印出來,如果我們需要將參數包中的每個參數打印出來的話就需要通過一些方法了。展開可變模版參數函數的方法一般有兩種:一種是通過遞歸函數來展開參數包,另外一種是通過逗號表達式來展開參數包。下面來看看如何用這兩種方法來展開參數包。

第一. 首先是通過遞歸函數的方法。

這種方法要求必須有明確的遞歸結束函數。比如下面的函數:

void print(){//遞歸結束函數
	cout << "this is the end!" << endl;
}

template<typename T, typename ...args>
void print(T t, args ... arg){//每次遞歸,都調用頭部參數進行操作
	cout << "this is arg: " << t << endl;
	print(arg...);
}

int main()
{
	print(1, 2, 3, 4, 5);
	system("pause");
	return 0;
}
/*
輸出是:
this is arg: 1
this is arg: 2
this is arg: 3
this is arg: 4
this is arg: 5
this is the end!
*/

我們觀察上述的過程,每次調用模板類中的print(args...)函數,都會取其中頭部的參數,剩下的參數則進行下一次的遞歸。也就是首先print(1,2,3,4,5)這個函數會對頭部參數1進行操作,之後遞歸調用print(2,3,4,5)...最後調用到print();這個函數是遞歸結束函數。也就是展開參數包的函數有兩個,一個是遞歸函數,另外一個是遞歸終止函數,參數包arg...在展開的過程中遞歸調用自己,每調用一次參數包中的參數就會少一個,直到所有的參數都展開爲止,當沒有參數時,則調用非模板函數print終止遞歸過程。

所以可變參數模板還可以模擬一個遞歸的過程,我們看下面的函數:

template <typename T>
T mul(T t){//遞歸終止函數,當遞歸的參數只有1個的時候返回結果並終止
	return t;
}
//展開函數
template<typename T, typename ...Types>
T mul(T head, Types ...rest){
	return head * mul<T>(rest...);
}

int main()
{
	cout << mul<int>(1, 2, 3, 4, 5) << endl;
	system("pause");
	return 0;
}

/*輸出結果是120*/

展開相乘函數mul的時候,使用遞歸的形式,分別用1*mul(2,3,4,5) -> 1 * 2 * mul(3,4,5)->...->1*2*3*4*mul(5) = 120;

第二種方式是逗號表達式

用遞歸的辦法很好理解,但是他要求必須有明確的遞歸結束函數。那麼有沒有辦法不用寫遞歸結束的函數呢?這就用到逗號表達式的解決辦法了。我們看下面的一個例子:

template<typename T>
void printarg(T t)
{
	//do nothing
}

template <class ...Args>
void expand(Args... args)
{
	int arr[] = { (printarg(args), 0)... };
	for (int i = 0; i < sizeof(arr)/4; i++){
		cout << arr[i] << " ";
	}
	cout << endl;
}

int main()
{
	expand(1, 2, 3, 4, 5);
	system("pause");
	return 0;
}

上面這個函數中的:

int arr[] = { (printarg(args), 0)... };

這句話實際上起到的是初始化一個長度爲args參數數量的int形數組。對於(printarg(args), 0)... 作爲可變參數模板,可以展開寫成:

(printarg(args[0]), 0),(printarg(args[1]), 0),(printarg(args[2]), 0) ... (printarg(args[n]), 0).而對於逗號表達式,c = (a,b) 就是按照順序執行c = b,所以最終上面式子的結果就是0,0,0,0...0。再加上外層的大括號作爲初始化,也就實際上展開爲:

int arr[] = {0,0,...0}一共n個0了。在創建數組的過程中會先執行逗號表達式前面的部分printarg(args)打印出參數(雖然這個函數什麼也沒做),也就是說在構造int數組的過程中就將參數包展開了,這個數組的目的純粹是爲了在數組構造的過程展示參數包的過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章