「C++ 进阶语法」可变参数模板与参数包展开输出
文章大图来源:pixiv_id=65463852
1 可变参数模板
1.1 基本概念
可变参数模板(variadic templates)是 C++ 11 引入的一个强大的特性。它允许模板函数或模板类接受可变数量的参数。这在编写泛型代码时非常有用,例如实现像 printf 这样可以接受不同数量和类型参数的函数。
1.2 基本语法
可变参数模板使用省略号 ...
来表示 参数包(parameter pack)。参数包可以包含零个或多个模板参数:
1 |
|
在上面的示例中,typename...
表明当前声明的是一个可变参数模板。Args
是一个模板参数包。args
是 Args...
类型的参数,也就是函数 work
的参数包(参数列表)。
Args
可以包含任意数量(包括零个)的不同类型的模板参数,args
则包含了实际传递给函数的参数。
例如下面的示例中,将 args
参数包通过 args...
(在后面加上省略号 ...
)展开,并存储到一个 std::tuple
元组中。
1 |
|
2 参数包展开输出方法
2.1 递归方式展开
假如我们要实现一个 print
函数,用于打印可变数量的参数。参数包的每个参数依次打印,并且用空格分隔,最终末尾输出回车符。
我们可以通过 可变参数模板 结合 递归 来实现。基本思路为:
- 定义一个递归终止函数,当参数包只有一个参数时调用,以停止递归。末尾输出回车符
- 定义一个递归函数,每次输出参数包的第一个参数。每个参数输出时后面加上空格符。
由此,可以得到如下实现:
1 |
|
在上面的代码中,print(T t)
是基础模板,同时也是递归终止函数。当只有一个参数时,后面输出回车符。
print(First first, Rest... rest)
是递归调用函数,首先打印第一个参数 first
,然后通过 print(rest...)
调用自身来处理剩余参数。rest...
也就是展开剩余的参数 rest
,作为新的参数列表并传递给下一层递归时的 print
函数调用。
2.2 逗号表达式展开
如何避免采用递归的方式实现展开参数包输出参数?以下是一个简单的实现代码:
1 |
|
在上面的代码中,定义了一个数组 arr
。通过类似于展开参数列表 args...
的方式,通过 (cout << args << ' ', 0)...
的方式来展开并输出。
(cout << args << " ", 0)
是一个 逗号表达式,当执行这个表达式时,会先执行 cout << args << ' '
这一句,然后执行 0
。而逗号表达式最终的值为最后一个逗号后面的子表达式的值,此处也就是 0
。
(cout << args << " ", 0)
后面加上省略号 ...
来展开参数包,对于参数包args
中的每一个元素,都会执行一次逗号表达式 (std::cout << args << " ", 0)
,也就是依次将每个参数输出。
而 arr
数组存储的元素全都是 0
,因此这里的数组并没有什么实际作用,只是通过其初始化列表的语法特性来协助展开参数包并输出。
当然,我们也可以使用一种轻量级的容器类型 std::initializer_list
来代替数组 arr
:
1 |
|
2.3 折叠表达式(C++ 17)
-
折叠表达式概念和语法
折叠表达式(fold expressions)是 C++ 17 引入的一个新特性,主要用于简化可变参数模板中对参数包的操作。它提供了一种简洁的方式来对参数包中的所有元素进行二元运算。例如,在处理像求和、求积等操作时,折叠表达式可以避免复杂的递归或迭代过程。
基本的语法形式为
(pack op...)
或者(... op pack)
,其中pack
是参数包,op
是一个二元运算符。例如
(args + ...)
就是一个折叠表达式,其中args
是一个参数包,+
是加法运算符。这个表达式会将参数包args
中的所有元素相加。 -
折叠方向
-
左折叠
语法形式为
(pack op...)
。它从 左到右 依次应用二元运算符。例如,对于参数包a, b, c
和运算符+
,左折叠表达式(a + b + c)等价于((a + b) + c)
。比如下面的示例:1
2
3
4
5
6
7
8
9
10
11
12#include <iostream>
template<typename... Args>
auto sum_left(Args... args) {
return (args + ...);
}
int main() {
std::cout << sum_left(1, 2, 3) << std::endl;
// 输出6,计算过程为((1 + 2)+3)
return 0;
} -
右折叠
语法形式为
(... op pack)
。它从 右到左 依次应用二元运算符。对于参数包a, b, c
和运算符+
,右折叠表达式(... + args)
等价于(a + (b + c))
。比如下面的示例:1
2
3
4
5
6
7
8
9
10
11
12#include <iostream>
template<typename... Args>
auto sum_left(Args... args) {
return (... + args);
}
int main() {
std::cout << sum_left(2, 3, 4) << std::endl;
// 输出9,计算过程为(2 + (3 + 4))
return 0;
}
-
以上是折叠表达式的基本概念和语法。
我们可以通过 折叠表达式 来实现可变参数模板的参数包展开和参数打印。由上面的语法,我们可以采用左折叠的形式,将 (cout << args << " ")
作为参数包,,
逗号作为运算符。这样我们可以通过 ((cout << args << ' '), ...)
的方式来展开参数包并依次输出,相当于执行了一个有若干个输出的逗号表达式。
以下是代码实现:
1 |
|