可变参数模板
基本语法及原理
C++11 支持可变参数模板,即支持可变数量参数的函数模板和类模板。可变数目的参数称为参数包,存在两种参数包:模板参数包(表示零或多个模板参数)和函数参数包(表示零或多个函数参数)。
template <class ...Args> void Func(Args&&... args) {}
使用省略号来指出一个模板参数或函数参数表示一个包。在模板参数列表中,class... 或 typename... 指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟 ... 指出接下来表示零或多个形参对象列表。函数参数包可以用左值引用或右值引用表示,遵循引用折叠规则。
可变参数模板的原理本质还是去实例化对应类型和个数的多个函数。可以使用 sizeof... 运算符计算参数包中参数的个数。
template <class ...Args> void Print(Args&&... args) { cout << sizeof...(args) << endl; }
int main() {
double x = 2.2;
Print(); // 包里有 0 个参数
Print(1); // 包里有 1 个参数
Print(1, string("xxxxx")); // 包里有 2 个参数
Print(1.1, string("xxxxx"), x); // 包里有 3 个参数
return 0;
}
编译本质会结合引用折叠规则实例化出对应的函数。有了可变参数模板,我们在类型泛化基础上叠加数量变化,让泛型编程更灵活。
包扩展
对于参数包,除了计算参数个数,主要操作是扩展它。扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。通过在模式的右边放一个省略号 (...) 来触发扩展操作。 C++ 还支持更复杂的包扩展,直接将参数包依次展开作为实参给一个函数处理。 不支持用下面的方式去进行包扩展(运行时无法获取解析):
template <class ...Args> void Print(Args... args) {
cout << sizeof...(args) << endl;
for (size_t i = 0; i < sizeof...(args); i++) {
cout << args[i] << " ";
}
cout << endl;
}
下面展示使用递归的一种包扩展方式。
方式一
void ShowList() {
cout << endl;
}
template <class T, class ...Args> void ShowList(T x, Args... args) {
cout << x << " ";
ShowList(args...);
}
template <class ...Args> void Print(Args... args) {
ShowList(args...);
}
int main() {
Print(1, string("xxxxx"), 2.2);
return 0;
}
编译器将可变参数模板通过模式的包扩展,推导出具体的重载函数。
方式二
template <class T> const T & GetArg(const T & x) {
cout << x << " ";
return x;
}
template <class ...Args> void Arguments(Args... args) { }
template <class ...Args> void Print(Args... args) {
Arguments(GetArg(args)...);
}
int main() {
Print(1, string("xxxxx"), 2.2);
return 0;
}
编译器在编译时将函数模板扩展实例化为具体的函数调用。
emplace 系列接口
C++11 以后 STL 容器新增了 emplace 系列的接口,如 emplace_back 和 emplace。这些接口均为模板可变参数,功能上兼容 push 和 insert 系列,但是 emplace 还支持直接插入构造对象的参数,这样有些场景会更高效一些,可实现原地构造,避免临时对象拷贝。

