一个可变参数模板(variable template)就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包(parameter packet)。存在两种参数宝:
- 模板参数包(template parameter packet),表示0个或多个模板参数
- 函数参数包(function parameter packet),表示0个或多个函数参数。
我们用一个省略号来指出一个模板参数或函数参数表示一个包。
- 在一个模板参数列表中,class... 或typename...指出接下来的参数表示0个或多个类型的列表;
- 一个类型名后面跟...表示0个或多个给定类型的非类型参数的列表。
- 在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是以个函数参数包
//Args是一个模板参数包;rest是一个函数参数包
//Args表示0个或多个模板类型参数
//rest表示0个或多个函数参数
template <typename T, typename... Args>
void foo(const T &t, const Args... rest);
当我们需要知道包中有多少元素时,可以使用sizeof...运算符,sizeof...运算符也返回一个常量表达式,且不对实参求值:
template<typename ... Args> void g(Args ... args)
{
cout << sizeof...(Args) << endl; //类型参数的数目
cout << sizeof...(args) << endl; //函数参数的数目
}
编写可变参数函数模板
我们可以使用一个initializer_list来定义一个可接受可变数目同种类型实参的函数。但实参类型不一致时,可变参数函数是很有用的。
可变参数函数通常是递归的。第一步调用处理保重的第一个实参,然后用剩余参数调用自身,print函数每次递归调用将第二个参数打印到第一个实参表示的流中。为了终止递归,还定义一个非可变参数的print函数
template<typename T>
ostream &print( ostream &os, const T &t)
{
return os << t;
}
template<typaname T, typename... Args>
ostream &print( ostream &os, const T &t, const Args&... rest)
{
os << t << ", ";
returen print(os, rest...);
}
当定义可变参数版本的print时,非可变参数版本的声明必须出现在作用域中。否则,可变参数版本会无限递归。
包扩展
对于一个参数包,除了获取其大小外,我们能做的唯一的事情就是扩展(expand)它。当扩展一个包时,我们还要提供用于每个扩展元素的模式(pattern)。扩张一个包就是将它分解成构成的元素,对每个元素应用模式,获得扩展后的列表。通过在模式右边放一个省略号(...)来触发扩展操作
template<typaname T, typename... Args>
ostream &
print( ostream &os, const T &t, const Args&... rest) //扩展Args
{
os << t << ", ";
returen print(os, rest...); //扩展rest
}
template <typename... Args>
ostream &
errorMsg(ostream &os, const Args&... rest)
{
return print(os, debug_rep(rest)...);
}
这个print调用使用了模式debug_rep(rest)。此模式表示我们希望对函数参数包rest中的每个元素调用debug_rep。扩展的结果将是一个逗号分隔的debug_rep调用列表。扩展中的模式会独立地应用于包中的每个元素。
转发参数包
我们可以组合使用可变参数模板与forward机制来编写函数,实现将其实参不变地传递给其他函数。
class StrVec {
public:
template <class... Args> void emplace_back(Args&&...);
};
template<class... Args>
inline
void StrVec::emplace_back(Args&&... args)
{
check_n_alloc();
alloc.construct(first_free++, std::forward<Args>(args)...);
}