0%

[c++11] 2.可变参数模板

introduction#

C++11的新特性--可变模版参数(variadic templates)是C++11新增的最强大的特性之一,它对参数进行了高度泛化,表示0到任意个数、任意类型的参数.

可变模版参数函数#

1
2
3
4
5
6
7
8
9
template <class... T>
void f(T... args)
{ //可以传任意类型和个数的参数给函数f
cout << sizeof...(args) << endl; //打印变参的个数
}

f(); //0
f(1, 2); //2
f(1, 2.5, ""); //3

递归函数方式展开参数包#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
//递归终止函数
void print()
{
cout << "empty" << endl;
}
//展开函数
template <class T, class ...Args>
void print(T head, Args... rest)
{
cout << "parameter " << head << endl;
print(rest...);
}

int main(void)
{
print(1,2,3,4);
return 0;
}

逗号表达式展开参数包#

1
2
3
4
5
6
7
8
9
10
11
12
13
template <class T>
void printarg(T t)
{
cout << t << endl;
}

template <class ...Args>
void expand(Args... args)
{
int arr[] = {(printarg(args), 0)...};
}

expand(1,2,3,4);

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式,比如:

1
d = (a = b, c); 
这个表达式会按顺序执行:b会先赋值给a,接着括号中的逗号表达式返回c的值,因此d将等于c

expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成

1
((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0),  etc... )
最终会创建一个元素值 都为0的数组int arr[sizeof...(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包
1
2
3
4
5
6
7
template<class F, class... Args>
void expand(const F& f, Args&&...args) 
{
//这里用到了完美转发,关于完美转发,读者可以参考笔者在上一期程序员中的文章《通过4行代码看右值引用》
  initializer_list<int>{(f(std::forward< Args>(args)),0)...};
}
expand([](int i){cout<<i<<endl;}, 1,2,3);
## reference 1.type ... Args(optional) 2.泛化之美--C++11可变模版参数的妙用