在(C++11)之前,我们可以通过C语言提供的变长函数实现一个可以接受任意长度参数列表的求和函数:
double sum(int count,...)
{
va_list ap;
va_start(ap, count);
double sum = 0;
for (int i = 0; i < count; i++)
sum += va_arg(ap, double);
va_end(ap);
return sum;
}
上面的例子虽然实现了可变长参数的函数,但是明显有着不便之处:
也就是说,实际上C语言提供的可变长函数,并没有实现完全意义上的变长参数。
而在C++11开始,引入了变长模板的概念,可以以此实现真正的变长参数。
根据变长模板应用于类模板还是函数模板分为模板参数包和函数参数包。
即模板参数包是带类型的,可以是相同类型,也可以是不同类型。
C++11变长模板的语法如下:
template class tuple;
以typename后的...表示该参数是变长的。其中Elements称为“模板参数包”,是一种新的模板参数类型。
而对于下面的实例化:
tuple
编译器进行类型推导时,会将int,char,double三个参数打包成一个模板参数包Elements。
有时我们可以确定所有类型都是相同的,那么可以指定模板参数包的参数类型,从而获得非类型的模板参数包:
template class NonTypeVariadicTemplate{};
NonTypeVariadicTemplate<1, 0, 2> ntvt;
这里A就包含了1,0,2三个值,而非类型。
除此之外还有模板类型的模板参数包,这个比较复杂,在后面展示。
一个模板参数包在模板推导时会被认为是模板的单个参数(虽然实际上它将会打包任意数量的实参)。为了使用模板参数包,我们总是需要将其解包(unpack)。在C++11中,这通常是通过一个名为包扩展(pack expansion)的表达式来完成,比如:
template class Template: private B{};
其中A...就是一个包扩展。更具体的如下:
template class B{};
template class Template: private B{};
Template xy;
第三句Template
那么我们如何通过变长模板来是的模板能够接收任意多参数呢?答案是递归。
template class tuple; // 变长模板的声明
template // 递归的偏特化定义
class tuple : private tuple {
Head head;
};
template<> class tuple<> {}; // 边界条件
可以看到对于tuple
此外,对于无类型版本的接收任意多参数的模板如下:
#include
using namespace std;
template struct Multiply;
template
struct Multiply {
static const long val = first * Multiply::val;
};
template<>
struct Multiply<> {
static const long val = 1;
};
int main() {
cout << Multiply<2, 3, 4, 5>::val << endl; // 120
cout << Multiply<22, 44, 66, 88, 9>::val << endl; // 50599296
return 0;
}
上述代码实现的是参数的乘积。而且,由于val声明的是static const,因此所有的计算都发生于编辑期(即运行期无函数调用),此种方式称为模板元编程。
当可变参数用于函数模板时,称为函数参数包,语法如下:
template void f(T ... args);
值得注意的是,在C++11中,标准要求函数参数包必须唯一,且是函数的最后一个参数(模板参数包没有这样的要求)。
值得注意的是,在C++11中,标准要求函数参数包必须唯一,且是函数的最后一个参数(模板参数包没有这样的要求)
#include
#include
using namespace std;
void Printf(const char* s) {
while (*s) {
if (*s == '%' && *++s != '%')
throw runtime_error("invalid format string: missing arguments");
cout << *s++;
}
}
template
void Printf(const char* s, T value, Args... args) {
while (*s) {
if (*s == '%' && *++s != '%') {
cout << value;
return Printf(++s, args...);
}
cout << *s++;
}
throw runtime_error("extra arguments provided to Printf");
}
int main() {
Printf("hello %s\n", string("world")); // hello world
}
// 编译选项:g++ -std=c++11 6-2-4.cpp
前面提到了解包的表达式称为包扩展,实际上包能够展开的地方是有限制的,标准定义了七种位置:
❑ 表达式
❑ 初始化列表(列表初始化参见第3章)
❑ 基类描述列表
❑ 类成员初始化列表
❑ 模板参数列表
❑ 通用属性列表(第8章中会讲到)
❑ lambda函数的捕捉列表(第7章中会讲到)
除此之外的其他地方则不允许解包。
而对于解包有几个有几个有趣的点。
对于声明了的变长模板参数包Args,如果我们使用Args&&...的方式解包,意味着展开后的参数为Arg1&&,Arg2&&,...,Argn&&。
对于表达式
template class T: private B...{};
与表达式
template class T: private B{};
所表达的含义是不一样的,前者表示多重继承B
#include
using namespace std;
template void DummyWrapper(T... t) {}
template T pr(T t) {
cout << t;
return t;
}
template
void VTPrint(A... a) {
DummyWrapper(pr(a)...); // 包扩展解包为pr(1), pr(", ")..., pr(", abc\n")
};
int main() {
VTPrint(1, ", ", 1.2, ", abc\n");
}
输出为
1, 1.2, abc
其中第10行采用pr(a)...的方式,表示将a展开后,逐个作为参数调用pr。而如果采用pr(a...)则表示pr接收模板参数包(当然这里并不支持),两者结果并不一样。
C++11引入变长模板的同时,扩展了sizeof,引入了sizeof...,用于计算参数包的参数个数,需要注意的是传入的参数为包名,而非形参名。
#include
#include
using namespace std;
template void Print(A...arg) {
assert(false); // 非6参数偏特化版本都会默认assert(false)
}
// 特化6参数的版本
void Print(int a1, int a2, int a3, int a4, int a5, int a6) {
cout << a1 << ", " << a2 << ", " << a3 << ", "
<< a4 << ", " << a5 << ", " << a6 << endl;
}
template int Vaargs(A...args){
int size = sizeof...(A); // 计算变长包的长度
switch(size){
case 0: Print(99, 99, 99, 99, 99, 99);
break;
case 1: Print(99, 99, args..., 99, 99, 99);
break;
case 2: Print(99, 99, args..., 99, 99);
break;
case 3: Print(args..., 99, 99, 99);
break;
case 4: Print(99, args..., 99);
break;
case 5: Print(99, args...);
break;
case 6: Print(args...);
break;
default:
Print(0, 0, 0, 0, 0, 0);
}
return size;
}
int main(void){
Vaargs(); // 99, 99, 99, 99, 99, 99
Vaargs(1); // 99, 99, 1, 99, 99, 99
Vaargs(1, 2); // 99, 99, 1, 2, 99, 99
Vaargs(1, 2, 3); // 1, 2, 3, 99, 99, 99
Vaargs(1, 2, 3, 4); // 99, 1, 2, 3, 4, 99
Vaargs(1, 2, 3, 4, 5); // 99, 1, 2, 3, 4, 5
Vaargs(1, 2, 3, 4, 5, 6); // 1, 2, 3, 4, 5, 6
Vaargs(1, 2, 3, 4, 5, 6, 7); // 0, 0, 0, 0, 0, 0
return 0;
}
实际上模板雷响的模板参数包和前面提到的类型模板参数包相似:
template class... B> struct Container{};
template class A, template class... B>
struct Container {
A a;
Container b;
};
template struct Container{};
这玩意可以将后面的template
#include
using namespace std;
struct A {
A(){}
A(const A& a) { cout << "Copy Constructed " << __func__ << endl; }
A(A&& a) { cout << "Move Constructed " << __func__ << endl; }
};
struct B {
B(){}
B(const B& b) { cout << "Copy Constructed " << __func__ << endl; }
B(B&& b) { cout << "Move Constructed " << __func__ << endl; }
};
// 变长模板的定义
template struct MultiTypes;
template
struct MultiTypes : public MultiTypes{
T1 t1;
MultiTypes(T1 a, T... b):
t1(a), MultiTypes(b...) {
cout << "MultiTypes(T1 a, T... b)" << endl;
}
};
template <> struct MultiTypes<> {
MultiTypes<>(){ cout << "MultiTypes<>()" << endl;}
};
// 完美转发的变长模板
template class VariadicType, typename... Args>
VariadicType Build(Args&&... args)
{
return VariadicType(std::forward(args)...);
}
int main() {
A a;
B b;
Build(a, b);
}
啥也不是。。。