1.概述
C++11的新特性--可變模版參數(variadic templates)是C++11新增的最強大的特性之一,它對參數進行了高度泛化,它能表示0到任意個數、任意類型的參數。相比C++98/03,類模版和函數模版中只能含固定數量的模版參數,可變模版參數無疑是一個巨大的改進。然而由於可變模版參數比較抽象,使用起來需要一定的技巧,所以它也是C++11中最難理解和掌握的特性之一。
雖然掌握可變模版參數有一定難度,但是它卻是C++11中最有意思的一個特性,本文希望帶領讀者由淺入深的認識和掌握這一特性,同時也會通過一些實例來展示可變參數模版的一些用法。
2.可變模版參數的展開
可變參數模板和普通模板的語義是一樣的,只是寫法上稍有區別,聲明可變參數模板時需要在typename或class后面帶上省略號“...”。比如我們常常這樣聲明一個可變模版參數:template<typename...>或者template<class...>,一個典型的可變模版參數的定義是這樣的:
template<typename... T> void fun(T... args) { }
上面的可變模版參數的定義當中,省略號的作用有兩個:
1.聲明一個參數包T... args,這個參數包中可以包含0到任意個模板參數;
2.在模板定義的右邊,可以將參數包展開成一個一個獨立的參數。
上面的參數args前面有省略號,所以它就是一個可變模版參數,我們把帶省略號的參數稱為“參數包”,它里面包含了0到N(N>=0)個模版參數。我們無法直接獲取參數包args中的每個參數的,只能通過展開參數包的方式來獲取參數包中的每個參數,這是使用可變模版參數的一個主要特點,也是最大的難點,即如何展開可變模版參數。
可變模版參數和普通的模版參數語義是一致的,所以可以應用於函數和類,即可變模版參數函數和可變模版參數類,然而,模版函數不支持偏特化,所以可變模版參數函數和可變模版參數類展開可變模版參數的方法還不盡相同,下面我們來分別看看他們展開可變模版參數的方法。
3.可變模板參數函數
一個簡單的可變模版參數函數:
template<typename... T> void fun(T... args) { // 打印參數個數 cout << sizeof...(args) << endl; }
上面的例子中,fun()沒有傳入參數,所以參數包為空,輸出的size為0,后面兩次調用分別傳入兩個和三個參數,故輸出的size分別為2和3。由於可變模版參數的類型和個數是不固定的,所以我們可以傳任意類型和個數的參數給函數f。這個例子只是簡單的將可變模版參數的個數打印出來,如果我們需要將參數包中的每個參數打印出來的話就需要通過一些方法了。展開可變模版參數函數的方法一般有兩種:一種是通過遞歸函數來展開參數包,另外一種是通過逗號表達式來展開參數包。下面來看看如何用這兩種方法來展開參數包。
3.1遞歸函數方式展開參數包
通過遞歸函數展開參數包,需要提供一個參數包展開的函數和一個遞歸終止函數,遞歸終止函數正是用來終止遞歸的,來看看下面的例子。
#include <iostream> using namespace std; // 最終遞歸函數 void print() { cout << "empty" << endl; } // 展開函數 template <typename T, typename... Args> void print(T head, Args... args) { cout << head << ","; print(args...); } int main() { print(1, 2, 3, 4); return 0; }
上例會輸出每一個參數,直到為空時輸出empty。展開參數包的函數有兩個,一個是遞歸函數,另外一個是遞歸終止函數,參數包Args...在展開的過程中遞歸調用自己,每調用一次參數包中的參數就會少一個,直到所有的參數都展開為止,當沒有參數時,則調用非模板函數print終止遞歸過程。
遞歸調用的過程是這樣的:
print(1,2,3,4);print(2,3,4);print(3,4);print(4);print();
上面的遞歸終止函數還可以寫成這樣:
template <class T>void print(T t){ cout << t << endl;}
修改遞歸終止函數后,上例中的調用過程是這樣的:
print(1,2,3,4);print(2,3,4);print(3,4);print(4);
當參數包展開到最后一個參數時遞歸為止。
我們將在下一博客中講解可變參數模板的其他使用方法。