【C++基础知识】折叠表达式详解--结合上一篇
折叠表达式(Fold Expressions)是 C++17 引入的一种模板元编程技术,用于简化可变参数模板(variadic templates)的参数包展开。它允许以简洁的方式对参数包(parameter pack)中的所有元素进行递归式操作,如求和、逻辑运算、函数调用等。
1. 基本语法
折叠表达式的基本形式如下:
折叠方式 | 语法示例 | 展开形式(假设参数包为 args... = a, b, c ) |
---|---|---|
一元右折叠 | (pack op ...) | (a op (b op c)) |
一元左折叠 | (... op pack) | ((a op b) op c) |
二元右折叠 | (pack op ... op init) | (a op (b op (c op init))) |
二元左折叠 | (init op ... op pack) | (((init op a) op b) op c) |
其中:
pack
是参数包(如args...
)。op
是操作符(如+
,&&
,,
等)。init
是初始值(仅用于二元折叠)。
2. 常见用途
(1) 求和(+
折叠)
template<typename... Args>
auto sum(Args... args) {return (args + ...); // 一元右折叠:a + b + c
}
展开形式:
return (a + (b + c));
(2) 逻辑运算(&&
或 ||
折叠)
template<typename... Args>
bool all_true(Args... args) {return (args && ...); // 检查所有参数是否为 true
}
展开形式:
return (a && (b && c));
(3) 逗号操作符折叠(,
折叠)
template<typename... Args>
void print_all(Args... args) {(std::cout << ... << args); // 一元左折叠:(((cout << a) << b) << c)
}
展开形式:
((std::cout << a) << b) << c;
3. 为什么使用逗号操作符(,
)折叠?
在最初的代码中:
((total_sum += Codec<remove_cvref_t<Args>>::compute_encoded_size(conditional_arg_size_cache, args)), ...);
使用的是逗号操作符折叠,而不是 +
折叠,原因如下:
(1) 保证求值顺序
-
+
折叠的求值顺序是未指定的(unspecified),编译器可以自由优化计算顺序:(args + ...); // 可能是 (a + b) + c,也可能是 a + (b + c)
如果
compute_encoded_size
有副作用(如修改conditional_arg_size_cache
),顺序不一致会导致错误。 -
,
折叠的求值顺序是严格从左到右(C++17 起):(f(args), ...); // 保证 f(a), f(b), f(c) 按顺序执行
因此,使用
,
折叠可以确保compute_encoded_size
按参数顺序依次执行。
(2) 避免 +
折叠的潜在问题
如果写成:
total_sum += (Codec<Args>::compute_encoded_size(args) + ...);
+
折叠可能先计算所有compute_encoded_size
再累加,导致conditional_arg_size_cache
更新顺序错误。- 而
,
折叠确保每次计算后立即累加,保证缓存正确更新。
4. 其他折叠表达式示例
(1) 二元左折叠(带初始值)
template<typename... Args>
auto sum_with_init(int init, Args... args) {return (init + ... + args); // 二元左折叠:(((init + a) + b) + c)
}
(2) 调用多个函数
template<typename... Funcs>
void run_all(Funcs... funcs) {(funcs(), ...); // 依次调用 func1(), func2(), func3()
}
(3) 检查是否所有参数满足条件
template<typename... Args>
bool all_even(Args... args) {return ((args % 2 == 0) && ...); // 检查是否全是偶数
}
5. 总结
特性 | 说明 |
---|---|
用途 | 简化可变参数模板的参数包展开 |
求值顺序 | + 、* 等数学运算顺序未指定,, 严格从左到右 |
适用操作符 | + , - , * , / , && , ` |
初始值支持 | 二元折叠((init op ... op pack) 或 (pack op ... op init) ) |
主要优势 | 代码简洁,避免递归模板展开 |
在最初的代码中,使用 ,
折叠是为了确保 compute_encoded_size
按顺序执行,从而正确更新 conditional_arg_size_cache
。这是折叠表达式在依赖求值顺序的场景下的典型应用。