如何使用折叠表达式实现std::tuple逐元素赋值并解决只读报错
问题描述
我定义了包装可变参数std::tuple的自定义类型Foo,目标是优先使用折叠表达式(而非std::index_sequence等相对繁琐的写法)实现加赋值运算符,对两个实例内tuple的元素逐对执行加赋值操作,预期运行输出为5 7 9。
原实现代码如下:
#include <iostream> #include <tuple> template <typename ...Args> struct Foo { std::tuple<Args...> t; Foo(Args&&... a) : t{ std::forward<Args>(a)... } { } Foo& operator +=(const Foo& f) { std::apply([&](auto&&... ts) { std::apply([...ts = std::forward<decltype(ts)>(ts)](auto&&... fts) { ((ts += fts), ...); }, f.t); }, t); return *this; } friend std::ostream& operator <<(std::ostream& os, const Foo& f) { std::apply([&](auto&&... ts) { ((os << ts << ' '), ...); }, f.t); return os; } }; int main() { Foo goy{ 1, 2, 3 }; Foo bar{ 4, 5, 6 }; goy += bar; std::cout << goy << std::endl; // expect 5 7 9 as output }
该代码无法通过编译:GCC抛出如下编译错误:
error: assignment of read-only variable 'ts#0' 15 | ((ts += fts), ...); | ~~~~^~~~~~~
Clang则触发内部编译器崩溃。
待确认问题:
- 上述代码是否不符合C++标准?
- 该问题是否为编译器bug?
- 是否存在优雅的修复方案?
问题原因
- 代码本身存在逻辑+语法双重错误,不符合C++标准:
- 内层lambda使用按值初始化捕获
...ts = std::forward<decltype(ts)>(ts),会将外层std::apply拿到的当前tuple元素拷贝一份存储在lambda内部,即使修改成功也只会改动副本,完全无法影响原tuple的元素,逻辑不成立。 - C++标准规定lambda的调用运算符默认带有
const限定,按值捕获的变量在lambda内部默认是只读的,直接对其执行赋值操作会触发const限定违规,这就是GCC报错的直接原因。
- 内层lambda使用按值初始化捕获
- Clang触发内部崩溃属于旧版本Clang对C++20参数包捕获场景的实现bug,并非标准规定的行为。
修复方案
优先使用折叠表达式、不依赖std::index_sequence的兼容C++20的修复写法非常简单,只需要将内层lambda的按值捕获改为按引用捕获参数包即可,避免拷贝同时获得修改权限:
Foo& operator +=(const Foo& f) { std::apply([&](auto&&... ts) { std::apply([&ts...](auto&&... fts) { ((ts += fts), ...); }, f.t); }, t); return *this; }
该写法完全符合标准:两个同类型Foo实例的tuple长度一致,折叠表达式会按顺序将对应位置的元素两两配对执行加赋值,没有多余拷贝,逻辑正确。
如果使用C++23及更新版本,还可以用更简洁的多tuplestd::apply写法,连嵌套lambda都不需要:
Foo& operator +=(const Foo& f) { std::apply([](auto&... self_elem, const auto&... other_elem) { ((self_elem += other_elem), ...); }, t, f.t); return *this; }
修复后代码运行即可输出预期结果5 7 9。
内容的提问来源于stack exchange,提问作者Jodocus
相关产品推荐
相关产品推荐

