变参模版,即参数数目可变的模版,如printf()。声明变参函数时,需令函数参数列表包含一个省略号(...),变参模版也是如此:
template
class my_template
{};
对于某个模版,即便其泛化版本的参数固定不变,也能用变参模版进行偏特化
(C++语法规定,任何模版必须具备泛化形式的声明,不能以偏特化形式声明,尽管泛化的packaged_task没有实际作用,其用途是告诉编译器存在名为packaged_task的模版):
template //无实际作用
class packaged_task; //无实际作用
template
class packaged_task;
我们可以凭代码std::packaged_task
特性:
1.普通模版参数(上例的ReturnType)和可变参数(上例的Args)能在同一声明内共存。
2.特化版本中使用了组合标记Args...,模版具现化时各型别依次列出。
3.模版实例化中,出现的型别被全部捕获并打包成Args,可变参数Args称为参数包(parameter pack),用“Args...”还原参数列表称为包的展开(pack expansion)。
在任何需要模版型别列表的场合,我们均可以直接运用展开式,在另一模版的参数列表中展开(展开模式省略号在后面),展开模式可随意设定复杂的型别表达式,前提是参数包在型别表达式中出现,且该表达式以省略号结尾:
template
class dummy
{
std::tuple data;
};
上面例子中,dummy类模版的参数在其tuple数据成员data的实例化中展开。
我们还可以依照某种模式创建元组,使其中的成员型别都是普通指针,甚至都是std::unique_ptr<>指针,目标型别对应参数包中的元素。
若模版参数列表用到了展开式,则模版无须再明文写出可变参数,参数包应准确匹配模版参数,所含参数数目必须相等:
template
struct dd
{
std::pair data;
};
dd a; //正确
dd b; //错误,缺少第二型别
dd b;//错误,型别数过多
另一种用途是声明函数参数:
template
void func(Args ...args);
一个函数的参数包还可以传递给另一个函数调用,后者在参数列表中设定好展开形式即可。以下例子通过std::forward<>保有函数参数的右值属性,“...”为展开位置:
template
void bar(Args&& ...args)
{
work(std::forward(args)...);
}
int i;
bar(i, 3.14, std::string("Circle"));
其展开形式为:
template<>
void bar(
int& args_1,
double&& args_2,
std::string&& args_3)
{
work(std::forward(args_1),
std::forward(args_2),
std::forward(args_3)
);
}
第一项参数为以左值引用方式传入,余下参数则以右值引用传递。
我们可以通过sizeof...(p)运算符,确定参数包所含元素数目,其中p为参数包:
template
unsigned count_args(Args ...args)
{
return sizeof...(Args);
}
sizeof...运算符求得的值是常量表达式,与普通sizeof运算符一样,故其结果可用于设定数组长度。