C++可变参数调用如何实现unary left fold与从左到右求值顺序
折叠表达式严格左到右求值传参实现方案
在折叠表达式展开为函数/仿函数参数列表的场景下,C++标准对普通函数参数的求值顺序未做强制规定,不同编译器、不同优化级别下可能出现逆序、乱序的情况,要保证严格从左到右的求值与传参顺序,可以按以下方案实现,全程无额外运行时开销。
核心规则依据
C++17及后续版本标准中,仅三类表达式明确保证操作数从左到右求值:
- 移位运算符
<</>>的两侧操作数 - 逗号运算符的左右操作数
- 花括号包围的初始化列表(brace-enclosed initializer list)中的所有初始化子句
其余场景下的求值顺序均为实现定义,r = f2(f(x)...)这类普通函数参数包展开的写法不存在顺序保证,观察到的逆序求值是部分编译器ABI下的常见表现,不属于可依赖的稳定行为。
零开销最优实现
利用初始化列表的顺序求值规则,可以在不引入中间tuple拷贝、不产生额外运行时开销的前提下实现严格左到右的求值传参,编译器在O2及以上优化级别下会通过RVO、常量折叠等优化完全消除临时结构,生成的汇编和手写顺序传参完全一致。
通用封装
#include <utility> #include <initializer_list> #include <functional> template<typename F, typename... Args> decltype(auto) strict_order_call(F&& target, Args&&... args) { if constexpr (sizeof...(args) == 0) { return std::forward<F>(target)(); } else { // 初始化列表严格从左到右求值所有参数,直接转发给目标函数 return [&]<std::size_t... Is>(std::index_sequence<Is...>) { auto arg_holder = std::initializer_list<std::reference_wrapper<std::remove_reference_t<Args>>>{ [&]() -> decltype(auto) { // 此处插入每个参数的转换逻辑,即原代码中的f(x)处理 return std::ref(std::forward<Args>(std::get<Is>(std::tuple<Args&&...>(std::forward<Args>(args)...)))); }()... }; return std::forward<F>(target)(arg_holder.begin()[Is].get()...); }(std::make_index_sequence<sizeof...(args)>{}); } }
注:
std::index_sequence是纯编译期构造,运行时不会生成任何指令,不存在所谓额外开销。
问题Demo修复
针对给出的复现代码,直接替换参数展开逻辑即可得到预期输出00112233:
#include <iostream> #include <tuple> #include <array> #include <initializer_list> int main () { constexpr std::array is{ 0,1,2,3 }; int eval{ 0 }; auto res = std::apply([&](auto&&... v) { std::tuple<> result_acc; // 初始化列表保证每个求值逻辑按参数顺序从左到右执行 (void)std::initializer_list<int>{ [&] { result_acc = std::tuple_cat( std::move(result_acc), std::tuple{v, eval++} ); return 0; }()... }; return result_acc; }, is); std::apply([](auto&&... v) { ((std::cout << v),...); }, res); }
其他常见方案的局限性
- 逗号运算符折叠
(f(x), ...):虽然保证左到右求值,但只能执行带副作用的逻辑,无法收集返回值聚合传给目标函数,仅适合不需要传参的场景。 - 左折叠写法
r = f2(...f(x)):属于非法C++语法,无法通过编译。 - 先构造
std::tuple{f(x)...}再std::apply传参:逻辑可行,但对于不可移动、拷贝成本高的返回值类型会产生额外的拷贝/移动开销,优化空间不如初始化列表直接转发的方案。
内容的提问来源于stack exchange,提问作者Chris G.
相关产品推荐
相关产品推荐

