C++ 折叠表达式
什么是折叠表达式?
折叠表达式(Fold Expressions)是C++17引入的一项新特性,它为处理可变参数模板提供了一种简洁优雅的方式。在C++17之前,要对参数包中的所有元素执行某种操作通常需要递归模板实例化,而折叠表达式允许我们使用单一表达式直接对参数包中的所有元素应用二元运算符。
备注
折叠表达式大大简化了可变参数模板的使用,使代码更加简洁和可读。
折叠表达式的基本语法
C++17中的折叠表达式有四种形式:
- 一元右折叠:
(pack op ...)
- 一元左折叠:
(... op pack)
- 二元右折叠:
(pack op ... op init)
- 二元左折叠:
(init op ... op pack)
其中:
pack
是参数包op
是二元运算符init
是初始值
折叠表达式的工作原理
让我们通过一个简单的例子来理解折叠表达式的工作原理:
cpp
#include <iostream>
// 使用一元右折叠计算参数之和
template<typename... Args>
auto sum(Args... args) {
return (args + ...);
}
int main() {
int result = sum(1, 2, 3, 4, 5);
std::cout << "Sum: " << result << std::endl;
return 0;
}
输出结果:
Sum: 15
在上面的示例中,(args + ...)
是一个一元右折叠表达式,它从右到左计算参数包中所有元素的和:
(1 + (2 + (3 + (4 + 5))))
不同类型的折叠表达式
一元右折叠
一元右折叠表达式 (pack op ...)
从右到左应用操作符:
cpp
template<typename... Args>
auto multiply(Args... args) {
return (args * ...);
}
// multiply(1, 2, 3, 4) 相当于 (1 * (2 * (3 * 4)))
一元左折叠
一元左折叠表达式 (... op pack)
从左到右应用操作符:
cpp
template<typename... Args>
auto multiply_left(Args... args) {
return (... * args);
}
// multiply_left(1, 2, 3, 4) 相当于 (((1 * 2) * 3) * 4)
二元右折叠
二元右折叠表达式 (pack op ... op init)
提供一个初始值:
cpp
template<typename... Args>
auto sum_with_init(Args... args) {
return (args + ... + 10); // 加上初始值10
}
// sum_with_init(1, 2, 3) 相当于 (1 + (2 + (3 + 10)))
二元左折叠
二元左折叠表达式 (init op ... op pack)
也提供初始值:
cpp
template<typename... Args>
auto sum_with_init_left(Args... args) {
return (10 + ... + args); // 加上初始值10
}
// sum_with_init_left(1, 2, 3) 相当于 (((10 + 1) + 2) + 3)
支持的运算符
折叠表达式支持C++中的大部分二元运算符,包括:
- 算术运算符:
+
,-
,*
,/
,%
- 位运算符:
&
,|
,^
,<<
,>>
- 逻辑运算符:
&&
,||
- 比较运算符:
==
,!=
,<
,>
,<=
,>=
- 逗号运算符:
,
- 赋值运算符及其组合形式:
=
,+=
,-=
,*=
等
折叠表达式的实际应用
1. 打印可变参数
cpp
#include <iostream>
template<typename... Args>
void print(Args... args) {
// 使用逗号运算符和输出运算符
(std::cout << ... << args) << std::endl;
}
int main() {
print("Hello", ' ', "world", '!', " C++17 is ", "awesome!");
return 0;
}
输出:
Hello world! C++17 is awesome!
2. 参数包合法性检查
cpp
#include <iostream>
#include <type_traits>
template<typename... Args>
bool all_positive(Args... args) {
return (... && (args > 0));
}
int main() {
std::cout << "All positive? " << all_positive(1, 2, 3, 4, 5) << std::endl;
std::cout << "All positive? " << all_positive(1, 2, -3, 4, 5) << std::endl;
return 0;
}
输出:
All positive? 1
All positive? 0
3. 批量推入元素到容器
cpp
#include <iostream>
#include <vector>
template<typename Container, typename... Args>
void push_back_many(Container& container, Args&&... args) {
(container.push_back(std::forward<Args>(args)), ...);
}
int main() {
std::vector<int> numbers;
push_back_many(numbers, 1, 2, 3, 4, 5);
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
输出:
1 2 3 4 5
4. 批量调用函数
cpp
#include <iostream>
struct Logger {
void log_info(const std::string& message) {
std::cout << "[INFO] " << message << std::endl;
}
void log_warning(const std::string& message) {
std::cout << "[WARNING] " << message << std::endl;
}
void log_error(const std::string& message) {
std::cout << "[ERROR] " << message << std::endl;
}
};
template<typename Object, typename... Functions>
void call_all(Object& obj, Functions... functions) {
(... , (obj.*functions)("Message"));
}
int main() {
Logger logger;
call_all(logger, &Logger::log_info, &Logger::log_warning, &Logger::log_error);
return 0;
}
输出:
[INFO] Message
[WARNING] Message
[ERROR] Message
空参数包的处理
当折叠表达式应用于空参数包时,结果取决于使用的运算符:
- 对于
&&
,结果为true
- 对于
||
,结果为false
- 对于
,
,结果为void()
- 对于其他运算符,会产生编译错误
可以使用二元折叠表达式来避免空参数包的问题:
cpp
template<typename... Args>
auto sum_safe(Args... args) {
return (args + ... + 0); // 对于空参数包,返回0
}
性能考虑
折叠表达式通常被编译器优化得很好。与递归模板实现相比,它们不会增加编译时间或运行时开销,反而可能因为简化了代码结构而提高性能。
总结
折叠表达式是C++17引入的一个强大功能,它极大地简化了可变参数模板的处理。通过简单直观的语法,我们可以对参数包中的所有元素应用统一操作,使代码更加简洁和可读。
折叠表达式的主要优点:
- 简化了处理可变参数模板的代码
- 提高了代码的可读性和可维护性
- 消除了对递归模板实例化的需求
- 支持广泛的二元运算符
练习
- 实现一个函数模板,使用折叠表达式检查所有参数是否都是相同类型
- 编写一个函数,使用折叠表达式计算参数包中所有元素的平均值
- 实现一个将所有参数转换为字符串并连接起来的函数
- 设计一个使用折叠表达式的元组打印工具
附加资源
提示
学习使用折叠表达式的最佳方式是通过实践。试着将你之前使用可变参数模板和递归实现的代码重构为使用折叠表达式的版本!