最近的C++語言標准有些更進一步的復雜特性,諸如加上了變長模板。我在嘗試理解這個特性的過程中
的一個最大的問題是,沒有足夠簡單的代碼示例來說明到底變長模板是如何使用和起作用的。
以下是 我的一個基本樣例來說明變長模板:
template <class ...A> int func(A... arg)
{
return sizeof...(arg);
}
int main(void)
{
return func (1,2,3,4,5,6);
}
首要介紹的是一些術語: 一個模板參數現在可以是一個模板參數包, <class...A>。一個模板參數包可以代表任意數量的
模板參數。在以上這個樣例中,模板 <class...A>定義了func這個函數,他擁有任意數量的函數參數。函數參數(A... arg)即為
一個函數 參數包,他以一個參數的"形式"代表了模板template <class...A>參數包的每個成員。在這 個示例中,我們使用了6個參數來調用函數func。
模板參數推導(template argument deduction)會將參數 包<class...A>推導成為
<int,int,int,int,int,int>,接著函數參數包變成 (int,int,int,int,int,int),正好對應了6個傳遞過來的整形參數。
變長操作符sizeof...簡單的返回了參 數包的參數個數(函數或者模板的),結果為6.
當然任何參數包多可以為空,考慮以下代碼示 例:
template <class ...A> int func(A... arg)
{
return sizeof... (arg);
}
int main(void)
{
return func();
}
在這個示例中模板參數包被推 導成為空,使得函數參數包即為空,那麼我們便不用傳遞任何參數也能進行函數調用,這裡,sizeof...的返 回結果為0。
另外,參數包中的參數也並不一定要同樣的類型:
template <class ...A> int func(A... arg)
{
return sizeof...(arg);
}
struct s1{}; struct s2{}; struct s3{};
int main(void)
{
s1 t1; s2 t2; s3 t3;
return func(t1,t2,t3);
}
在這個示例中參數包被推導為<s1,s2,s3>。
在之前的例子中,參數包都是類型(type parameter)參 數, 當然我們也可以指定非類型參數(non-type parameter), 比如:
template <bool ...A> int func()
{
return sizeof...(A);
}
int main(void)
{
return func<true,false,true,false>();
}
以上這個例子展示了變長模板的一些基本語法。得到參數包 中元素個數很簡單,但是如果一旦有元素不可被訪問,那麼參數包的作用
將會非常有限。
為了訪 問函數模板中的參數包,我們可以重載函數, 見以下示例:
#include <iostream>
using namespace std;
void func()
{
cerr << "EMPTY" << endl;
}
template <class A, class ...B> void func(A argHead, B... argTail)
{
cerr << "A: " << argHead << endl;
func (argTail...);
}
int main(void)
{
func(1,2,3,4,5,6);
return 0;
}
在這個示例中,我們有兩個函數名匹配函數調用,其一是非模板。由於非模板的那個匹配沒有相應 的函數參數,因此我們實際上只有一個匹配,
所有的相應調用應該選用模板的那個匹配。為了匹配一個模 板函數,編譯器不得不從函數調用的實參推導模板參數,並且實例化模板函數。這個
模板函數的第一個參 數是普通的模板參數<class A>,第二個是函數參數包。第一個模板參數會被推導成第一個參數的類型 。參數包中的每個成員類型
會基於所有還未推導的元素做推導(deduction)。
我們一開始便在main函數 中調用func(1,2,3,4,5,6),模板參數<class A>被推導成int,模板參數包<class...B>被推導成 <int,int,int,int,int>。
編譯器實例化了函數模板void func(int,int,int,int,int,int)。在函數 體內部的實例化,argTail...是包擴展(pack expansion)。一個包擴展是由
編譯器根據上下文語境來進行 擴展的。在這個示例中,argTail就是(2,3,4,5,6). 第一個參數是argHead,他現在是一個正常的函數參數。
接著,我們繼續函數調用的過程,現在調用是func(2,3,4,5,6),我們開始參數推導,在參數推導完成 之後,現在模板參數<class A>被推導為<int>,
模板參數包<class...B>被推導成為 <int,int,int,int>。在函數體內部argHead現在就是2,argTail就是(3,4,5,6),新的函數調用是func (3,4,5,6)
同理,我們繼續函數調用的過程,循環往復。下一次推導之後argHead是3,argTail是 (4,5,6), 新的函數調用是func(4,5,6)。再接下來,
下一次調用的過程,參數推導之後argHead是4, argTail是(5,6),新的函數調用是func(5,6)。再接著,argHead是5,argTail是(6),新的函數調用
是 func(6)。繼續下一步,argHead是6,argTail是(),新的函數調用是func()。到了這裡,我們發現現在的函數 調用匹配了非模板的那個func函數!
因此,基於以上分析,輸出結果為:
A: 1
A: 2
A: 3
A: 4
A: 5
A: 6
EMPTY