簡介
該篇博客主要介紹C++11中的變長模板,對變長模板的原理和使用方法進行介紹。本篇博客參考書籍深入理解C++11新特性解析與應用一書,非常推薦該書作爲C++11學習的參考資料,英語好的話更推薦直接閱讀C++官網中的關於C++11新特性的介紹。
該本書我已經上傳高清pdf版本,並且包含非常詳細的書籤,下載地址如下(爲上傳修改了名稱,放心下載):
C++11新特性解析與應用
模板和函數參數包
模板參數包說明
變長類模板的形式如下:
template <typename... Elements> class tuple;
標識符Elements
之前的使用了省略號來表示該參數是變長的。在C++11中,Elements
就被稱作是一個模板參數包(template parameter pack)
,這是一種新的模板參數類型。它可以接受任意多個參數作爲模板參數,實例化的tuple
模板類如下所示:
tuple<int, char, double>
與普通的模板參數類似,模板參數包也可以是非類型的,例如
template<int... A> class NonTypeVT { };
NonTypeVT<1,0,2> vtvt;
除了類型的模板參數包和非類型的模板參數包,模板參數包實際上還是模板類型的,後面討論。
解包
爲了使用模板參數包,我們需要將其解包(unpack)。在C++11中,通過**包拓展(pack expansion)**的表達式來完成。例如:
template<typename... A> class Template: private B<A...> { };
這裏的表達式A...
就是一個包拓展。參數包會在包拓展的位置展開爲多個參數。比如:
template<typename T1, typename T2> class B {};
template<typename... A> class Template: private B<A...> { };
Template<X, Y> xy;
上面的列子中展示了包拓展的實現,但是該例子基於類模板B總是接受兩個參數的前提下。若我們在這裏聲明瞭一個Template<X, Y, Z>
,就必然會發生模板推導的錯誤。爲了解決上述問題,見後面內容中提出的解決辦法。
在可變參數模板中使用遞歸
通過定義遞歸的模板偏特化定義,我們可以使得參數包在實例化時能夠層層展開,直到參數包中的參數逐漸耗盡或到達某個數量的邊界爲止。
類模板使用遞歸代碼如下所示(模板參數包):
template<typename... Elements> class tuple; // 變長模板的聲明
template<typename Head, typename... Tail> // 遞歸的偏特化定義
class tuple<Head, Tail...> : private tuple<Tail...> {
Head head;
}
template<> class tuple<> {}; // 邊界條件
上述代碼中偏特化版本的tuple
包含了兩個參數,一個是類型模板參數Head
,另一個則是模板參數包Tail
。tuple<double, int, char, float>
會引起基類的遞歸構造,這樣的遞歸構造在tuple
的參數包爲0個的時候會結束。這是由於我們定義了邊界條件,即編譯器從tuple<>
建造出tuple<float>
。
函數模板中使用地推代碼如下所示(函數參數包):
// definition for 0 parameters -- terminating call
void show_list3() {}
// definition for 1 or more parameters
template<typename T, typename... Args>
void show_list3( T value, Args... args)
{
std::cout << value << ", ";
show_list3(args...);
}
模板參數包與函數參數包的區別是在C++11中,標準要求函數參數包必須唯一,且是函數的最後一個參數,而模板參數包則沒有這樣的要求。
進階
C++11標準定義了以下7種參數包可以展開的位置:
- 表達式
- 初始化列表
- 基類描述列表
- 類成員初始化列表
- 模板參數列表
- 通用屬性列表
- lambda函數的捕捉列表
不同的包拓展方式
template<typename... A> class T: private B<A>... { }; // (1)
template<typename... A> class T: private B<A...> { }; // (2)
上述兩種在基類描述列表中解包後是不同的,對於同樣的實例化T<X, Y>
,解包後的形式如下:
class T<X, Y>: private B<X>, private<Y> { }; // (1)
class T<X, Y>: private B<X, Y> { }; // (2)
類似的狀況也會發生在函數模板中。
template<class... A> int Vaargs(A... args) {
int size = sizeof...(A);
}
在C++11中引入了新操作符sizeof...
,它的作用是計算參數包中的參數個數。