可变模板参数函数
可变参数模板的声明一般是template<class…>
Template<class... T> void func(T... args);
|
args
前面有省略号,我们把这种带省略号的参数称为”参数包”,可以包含0到任意个模板参数。
一个简单的例子:
template <class... T> void F(T... args) { cout << sizeof...(args) << endl; }
void test_param() { F(); F(1, 2); F(1, "", 2); }
0 2 3
|
递归方式展开可变参数
我们无法直接获取参数包args
中的每一个参数,只能通过展开参数包的方式来获取每一个参数。这是可变模板参数的一个主要特点。
要想实现这种展开,我们需要实现2个函数:展开函数和递归终止函数。
void print() { cout << "empty" << endl; }
template <class T, class ...Args> void print(T head, Args... rest) { cout << "parameter " << head << endl; print(rest...); }
int main() { print(1, 2, 3, 4); return 0; }
parameter 1 parameter 2 parameter 3 parameter 4 empty
|
这个例子用来打印出每一个参数,包含展开函数和递归终止函数。rest…
后面的省略号能把参数展开,首先我们可以把参数1
匹配到head
,剩下的2、3、4
匹配到参数包rest
。这里把参数1打印出来,然后参数包rest…
展开成2、3、4
。当参数展开完后,就调用递归终止函数,打印empty
。
再看一个简单的求和计算的例子:
#include <iostream>
template<class T> T sum(const T& val) { return val; }
template<class T, class... Args> T sum(const T& val, const Args&... args) { return val + sum(args...); }
int main() { std::cout << sum(1, 2, 3) << std::endl; return 0; }
6
|
当两个函数模板的区别只有尾部的可变参数包时,会优先选择没有参数包的那个函数模板。
逗号表达式+初始化列表展开参数包
首先我们通过代码看一下利用初始化列表展开参数包的效果。
template<class... Args> void init(Args... args) { int arr[] = {(args+100)...}; for(int i = 0; i < sizeof...(args); ++i){ std::cout << arr[i] << std::endl; } }
int main() { init(1, 2, 3); return 0; }
101 102 103
|
从上面可以看出在初始化列表中,省略号对args
进行了展开,这里会将args
展开成(args0+100)
、(args1+100)
、(args2+100)
的形式。现在我们再加上逗号表达式来展开可变参数,重新实现上面的可变参数打印。
template <class T> void printarg(T t) { cout << t << endl; }
template <class ...Args> void expand(Args... args) { int arr[] = {(printarg(args), 0)...}; }
int main() { expand(1,2,3); return 0; }
1 2 3
|
(printarg(args), 0)
这个表达式返回的值是0(逗号表达式的原因,需要给数组进行赋值),参数展开成 (printarg(args0), 0)
、(printarg(args1), 0)
、(printarg(args2,0))
。最后arr
变成了一个初始值全是0的数组。我们可以把上面的例子改进下,使用lambda
表达式来实现,更加简洁。
template<class F, class... Args> void nexpand(const F& f, Args&&...args) { initializer_list<int>{(f(std::forward< Args>(args)),0)...}; }
int main() { nexpand([](int val){ std::cout << val << std::endl; }, 1, 2, 3); return 0; }
1 2 3
|
c++17的折叠表达式
折叠表达式语法说明
c++17提供了一种折叠表达式的黑科技来展开可变参数。一共有四种形式:一元右折叠、一元左折叠;二元右折叠、二元左折叠。
- 一元右折叠:(
$E$
op …)后变成了$E_{1}$
op (… op ($E_{n-1}$
op $E_{n}$
))
- 一元左折叠:(… op
$E$
)后变成了(($E_{1}$
op $E_{2}$
) op …) op $E_{n}$
- 二元右折叠:(
$E$
op … op $I$
)后变成了$E_{1}$
op (… op ($E_{n-1}$
op ($E_{n}$
op $I$
) ))
- 二元左折叠:(
$I$
op … op $E$
)后变成了((($I$
op $E_{1}$
) op $E_{2}$
) op …) op $E_{n}$
上面公式中的$E$
表示参数包,$E_{i}$
表示第$i$
个参数,op表示操作符,$I$
表示初始参数。
新的“打印”应用
直接看下面的样例,提供了两种实现,一种是直接在函数内部进行打印,但是打印方式受到限制,无法定制化打印。第二种传入一个打印函数,可以进行定制化打印。
template<class... Rest> void print17(Rest&&... rest) { (std::cout << ... << rest) << std::endl; }
template<class Func, class... Rest> void expand17(Func&& func, Rest&&... rest) { (func(rest), ...); }
print17(1, 2, 3); expand17([](int a){ std::cout << a << " "; }, 1, 2, 3);
123 1 2 3
|
可变模板参数类
可变参数模板类是一个带可变模板参数的模板类,比如元组std::tuple就是一个可变参数模板类。
template<class... Args> class tuple;
|
模板偏特化和递归方式展开参数包
可变模板参数类的展开方式比可变模板参数函数要复杂一些。需要对类进行声明,提供一个类的基本定义和一个偏特化的递归终止类。
template<typename... Args> struct Sum;
template<typename First, typename... Rest> struct Sum<First, Rest...> { enum { value = Sum<First>::value + Sum<Rest...>::value }; };
template<typename Last> struct Sum<Last> { enum { value = sizeof (Last) }; };
int main() { std::cout << Sum<int, double, int>::value << std::endl; return 0; }
16
|
参考