變參模板:就是可以使用不定數(shù)量的參數(shù)進行特化的模板。就像你接觸到的變參函數(shù)一樣,printf就接受可變參數(shù)?,F(xiàn)在,就可以給你的模板指定不定數(shù)量的參數(shù)了。變參模板在整個C++線程庫中都有使用,例如:std::thread的構(gòu)造函數(shù)就是一個變參類模板。從使用者的角度看,僅知道模板可以接受無限個參數(shù)就夠了,不過當要寫這么一個模板或?qū)ζ涔ぷ髟砗芨信d趣時,就需要了解一些細節(jié)。
和變參函數(shù)一樣,變參部分可以在參數(shù)列表章使用省略號...代表,變參模板需要在參數(shù)列表中使用省略號:
template<typename ... ParameterPack>
class my_template
{};
即使主模板不是變參模板,模板進行部分特化的類中,也可以使用可變參數(shù)模板。例如,std::packaged_task<>(見4.2.1節(jié))的主模板就是一個簡單的模板,這個簡單的模板只有一個參數(shù):
template<typename FunctionType>
class packaged_task;
不過,并不是所有地方都這樣定義;對于部分特化模板來說,其就像是一個“占位符”:
template<typename ReturnType,typename ... Args>
class packaged_task<ReturnType(Args...)>;
部分特化的類就包含實際定義的類;在第4章,可以寫一個std::packaged_task<int(std::string,double)>來聲明一個以std::string和double作為參數(shù)的任務(wù),當執(zhí)行這個任務(wù)后結(jié)果會由std::future<int>進行保存。
聲明展示了兩個變參模板的附加特性。第一個比較簡單:普通模板參數(shù)(例如ReturnType)和可變模板參數(shù)(Args)可以同時聲明。第二個特性,展示了Args...特化類的模板參數(shù)列表中如何使用,為了展示實例化模板中的Args的組成類型。實際上,因為這是部分特化,所以其作為一種模式進行匹配;在列表中出現(xiàn)的類型(被Args捕獲)都會進行實例化。參數(shù)包(parameter pack)調(diào)用可變參數(shù)Args,并且使用Args...作為包的擴展。
和可變參函數(shù)一樣,變參部分可能什么都沒有,也可能有很多類型項。例如,std::packaged_task<my_class()>中ReturnType參數(shù)就是my_class,并且Args參數(shù)包是空的,不過std::packaged_task<void(int,double,my_class&,std::string*)>中,ReturnType為void,并且Args列表中的類型就有:int, double, my_class&和std::string*。
變參模板主要依靠包括擴展功能,因為不能限制有更多的類型添加到模板參數(shù)中。首先,列表中的參數(shù)類型使用到的時候,可以使用包擴展,比如:需要給其他模板提供類型參數(shù)。
template<typename ... Params>
struct dummy
{
std::tuple<Params...> data;
};
成員變量data是一個std::tuple<>實例,包含所有指定類型,所以dummy<int, double, char>的成員變量就為std::tuple<int, double, char>。
可以將包擴展和普通類型相結(jié)合:
template<typename ... Params>
struct dummy2
{
std::tuple<std::string,Params...> data;
};
這次,元組中添加了額外的(第一個)成員類型std::string。其優(yōu)雅指出在于,可以通過包擴展的方式創(chuàng)建一種模式,這種模式會在之后將每個元素拷貝到擴展之中,可以使用...來表示擴展模式的結(jié)束。
例如,創(chuàng)建使用參數(shù)包來創(chuàng)建元組中所有的元素,不如在元組中創(chuàng)建指針,或使用std::unique_ptr<>指針,指向?qū)?yīng)元素:
template<typename ... Params>
struct dummy3
{
std::tuple<Params* ...> pointers;
std::tuple<std::unique_ptr<Params> ...> unique_pointers;
};
類型表達式會比較復雜,提供的參數(shù)包是在類型表達式中產(chǎn)生,并且表達式中使用...作為擴展。當參數(shù)包已經(jīng)擴展 ,包中的每一項都會代替對應(yīng)的類型表達式,在結(jié)果列表中產(chǎn)生相應(yīng)的數(shù)據(jù)項。因此,當參數(shù)包Params包含int,int,char類型,那么std::tuple<std::pair<std::unique_ptr<Params>,double> ... >將擴展為std::tuple<std::pair<std::unique_ptr<int>,double>,std::pair<std::unique_ptr<int>,double>,std::pair<std::unique_ptr<char>, double> >。如果包擴展被當做模板參數(shù)列表使用,那么模板就不需要變長的參數(shù)了;如果不需要了,參數(shù)包就要對模板參數(shù)的要求進行準確的匹配:
template<typename ... Types>
struct dummy4
{
std::pair<Types...> data;
};
dummy4<int,char> a; // 1 ok,為std::pair<int, char>
dummy4<int> b; // 2 錯誤,無第二個類型
dummy4<int,int,int> c; // 3 錯誤,類型太多
可以使用包擴展的方式,對函數(shù)的參數(shù)進行聲明:
template<typename ... Args>
void foo(Args ... args);
這將會創(chuàng)建一個新參數(shù)包args,其是一組函數(shù)參數(shù),而非一組類型,并且這里...也能像之前一樣進行擴展。例如,可以在std::thread的構(gòu)造函數(shù)中使用,使用右值引用的方式獲取函數(shù)所有的參數(shù)(見A.1節(jié)):
template<typename CallableType,typename ... Args>
thread::thread(CallableType&& func,Args&& ... args);
函數(shù)參數(shù)包也可以用來調(diào)用其他函數(shù),將制定包擴展成參數(shù)列表,匹配調(diào)用的函數(shù)。如同類型擴展一樣,也可以使用某種模式對參數(shù)列表進行擴展。
例如,使用std::forward()以右值引用的方式來保存提供給函數(shù)的參數(shù):
template<typename ... ArgTypes>
void bar(ArgTypes&& ... args)
{
foo(std::forward<ArgTypes>(args)...);
}
注意一下這個例子,包擴展包括對類型包ArgTypes和函數(shù)參數(shù)包args的擴展,并且省略了其余的表達式。
當這樣調(diào)用bar函數(shù):
int i;
bar(i,3.141,std::string("hello "));
將會擴展為
template<>
void bar<int&,double,std::string>(
int& args_1,
double&& args_2,
std::string&& args_3)
{
foo(std::forward<int&>(args_1),
std::forward<double>(args_2),
std::forward<std::string>(args_3));
}
這樣就將第一個參數(shù)以左值引用的形式,正確的傳遞給了foo函數(shù),其他兩個函數(shù)都是以右值引用的方式傳入的。
最后一件事,參數(shù)包中使用sizeof...操作可以獲取類型參數(shù)類型的大小,sizeof...(p)就是p參數(shù)包中所包含元素的個數(shù)。不管是類型參數(shù)包或函數(shù)參數(shù)包,結(jié)果都是一樣的。這可能是唯一一次在使用參數(shù)包的時候,沒有加省略號;這里的省略號是作為sizeof...操作的一部分,所以不算是用到省略號。
下面的函數(shù)會返回參數(shù)的數(shù)量:
template<typename ... Args>
unsigned count_args(Args ... args)
{
return sizeof... (Args);
}
就像普通的sizeof操作一樣,sizeof...的結(jié)果為常量表達式,所以其可以用來指定定義數(shù)組長度,等等。