可变参
1 2
| template <typename ...Args> void func(Args ...args);
|
args 叫做函数参数包,里面包含传递的参数。
Args 叫做模板参数包,里面包含传递参数的类型。
说明:省略号写在参数包的左边,代表打包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| template<typename ...Args> void display(Args ...args) { cout << "参数个数 " << sizeof...(args) << endl; cout << "参数类型个数 " << sizeof...(Args) << endl; }
int main() { display("xy", 18, "Student", 65.5); return 0; }
|
光会这个没什么价值,我们肯定希望能够把每个变量都可以读取到。
需要对参数包进行解包(展开)。每次解出第一个参数,然后递归调用函数模板,直到递归出口。
注:省略号写在参数包的右边,代表解包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void display() { cout << "over" << endl; }
template<typename T, typename... Args> void display(T first, Args... args) { cout << first << endl;
display(args...); }
int main() {
display("xy", 18, "Student", 65.5);
return 0; }
|
当 display("xy", 18, "Student", 65.5)
被调用时,递归展开如下:
- 第一次调用:
first = "xy"
, args = 18, "Student", 65.5
,打印 "xy"
,然后递归调用 display(18, "Student", 65.5)
。
- 第二次调用:
first = 18
, args = "Student", 65.5
,打印 18
,然后递归调用 display("Student", 65.5)
。
- 第三次调用:
first = "Student"
, args = 65.5
,打印 "Student"
,然后递归调用 display(65.5)
。
- 第四次调用:
first = 65.5
, args = {}
(空),打印 65.5
,然后递归调用 display()
。
- 最终调用:没有参数时,调用无参的
display()
,打印 "over"
。
当我们想要获取全部的参数类型,重新定义一个可变参数模板,至少得有一个参数,因此我们在前面的代码基础上额外添加一个 typename T 。
1 2 3 4 5 6 7 8 9
| 最初:
template <typename ...Args> void func(Args ...args);
为了展开:
template<typename T, typename... Args> void display(T first, Args... args);
|
由于递归之后,必然会遇到没有参数,所以需要单独写一个没有参数的同名函数,用以结束。
那如果,我们希望一时获取两个参数,或者更多参数呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void display() { cout << "over" << endl; }
template<typename T, typename G,typename... Args> void display(T first,G second, Args... args) { cout << first << " " << second << endl;
display(args...); } int main() {
display("xy", 18, "Student");
return 0; }
|
传递三个参数,但是我们每次都要展开两个参数,必然会剩下一个参数,但是我们并没有额外写一个同名函数接受单个参数,因此编译报错。解决方案见下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void display() { cout << "over" << endl; }
template<typename T> void display(T arg) { cout << arg << endl; }
template<typename T, typename G,typename... Args> void display(T first,G second, Args... args) { cout << first << " " << second << endl;
display(args...); }
|
递归的出口可以使用普通函数或者普通的函数模板,但是规范操作是使用普通函数。
(1)尽量避免函数模板之间的重载;
(2)普通函数的优先级一定高于函数模板,更不容易出错。
如上是函数模板中的形参包展开,类模板中同样是如此的展开方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| template <typename... Args> class Printer { public: Printer(Args... args) { print(args...); }
private:
void print() { std::cout << "End of arguments\n"; }
template <typename T, typename... Rest> void print(T first, Rest... rest) { std::cout << first << " "; print(rest...); } };
|
变量模板是 C++17 引入,而折叠表达式也是 C++17 引入,当我们还是在这里介绍 变量模板的展开:
1 2 3 4 5 6 7 8
| template<std::size_t...values> constexpr std::size_t array[]{ values... };
int main() { for (const auto& i : array<1, 2, 3, 4, 5>) { std::cout << i << ' '; } }
|
array 是一个数组,遍历这个数组进行展开。
折叠表达式
C++11 引入了可变参数模板(variadic template),它可以接收任意数量的模板参数,但是参数包不能直接展开,需要通过递归或者逗号表达式的方式进行展开,写法非常繁琐。C++17 对这个问题进行了优化,引入了折叠表达式的概念,用来简化对可变参数模板中参数包的展开过程。
1 2 3 4 5 6 7 8
| (pack op ...)
(... op pack)
(pack op ... op init)
(init op ... op pack)
|
注:折叠表达式是左折叠还是右折叠,取决于 ...
是在“pack”的左边还是右边
op
:折叠表达式支持如下 32 个 二元运算符
1
| +, -, *, /, %, ^, &, |, =, <, >, <<, >>, +=, -=, *=, /=, %=, ^=, &=, |=, <<=, >>=, ==, !=, <=, >=, &&, ||, ,, .*, ->*
|
注:在折叠表达式中,所有op
操作符必须相同
pack
:含有未展开的参数包,且在顶层不含优先级低于转型表达式的运算符的表达式
init
:不含未展开的参数包,且在顶层不含优先级低于转型表达式的运算符的表达式
()
:括号也是折叠表达式的一部分
...
:折叠标记
一元折叠
一元右折叠
1 2 3 4
| template<typename... Args> void display(Args... args) { ((cout << args<<" "),...); }
|
pack
:(cout << args<<" ")
op
:,
...
:永远保持不变
一元左折叠
1 2 3 4
| template<typename... Args> void display(Args... args) { (...,(cout << args<<" ")); }
|
左右区别的影响在哪里?
上面的两个示例是为了方便学习,但这容易给人一种错误的认知,就是左右折叠并无二别。
接下来我引入 -
运算符,再来试试看:
1 2 3 4 5 6 7 8 9 10
| template<int...I> constexpr int v_right = (I - ...);
template<int...I> constexpr int v_left = (... - I);
int main(){ std::cout << v_right<4, 5, 6> << '\n'; std::cout << v_left<4, 5, 6> << '\n'; }
|
二元折叠
1 2 3 4 5 6 7 8 9
| template<int...I> constexpr int v = (I + ... + 10);
template<int...I> constexpr int v2 = (10 + ... + I);
std::cout << v<1, 2, 3, 4> << '\n'; std::cout << v2<1, 2, 3, 4> << '\n';
|