如何为std::tuple包装类启用结构化绑定并解决编译错误
私有Tuple成员Widget的结构化绑定包装类问题
场景与现有代码
Widget类定义
class widget { friend class wrap; std::tuple<int, double> m_tuple {1, 1.0}; };
Wrap包装类实现
class wrap { public: wrap(widget& f) : m_tuple(f.m_tuple) {} // auto some_other_function(); template<std::size_t Index> auto get() & -> std::tuple_element_t<Index, wrap>& { return std::get<Index>(m_tuple); } template<std::size_t Index> auto get() && -> std::tuple_element_t<Index, wrap>& { return std::get<Index>(m_tuple); } template<std::size_t Index> auto get() const& -> const std::tuple_element_t<Index, wrap>& { return std::get<Index>(m_tuple); } template<std::size_t Index> auto get() const&& -> const std::tuple_element_t<Index, wrap>& { return std::get<Index>(m_tuple); } private: std::tuple<int, double>& m_tuple; };
std库特化
namespace std { template<> struct tuple_size<wrap> : tuple_size<std::tuple<int, double>> {}; template<size_t Index> struct tuple_element<Index, wrap> : tuple_element<Index, tuple<int, double>> {}; } // namespace std
预期行为
希望通过结构化绑定实现两种操作:
int main() { widget w; auto [i_copy, d_copy] = wrap(w); i_copy = 2; // 不修改w.m_tuple,因为是副本 d_copy = 2.0; // 不修改w.m_tuple,因为是副本 // w.m_tuple 仍为 {1, 1.0} auto& [i_ref, d_ref] = wrap(w); i_ref = 2; // 修改w.m_tuple,因为是引用 d_ref = 2.0; // 修改w.m_tuple,因为是引用 // w.m_tuple 变为 {2, 2.0} }
编译报错
在gcc 12.2.0和clang 14.0.6下编译失败,报错信息:
error: cannot bind non-const lvalue reference of type ‘wrap&’ to an rvalue of type ‘wrap’ | auto& [i_ref, d_ref] = wrap(w);
用户疑问
- 报错中的
non-const lvalue reference of type ‘wrap&’是否指auto& [i_ref, d_ref],rvalue of type ‘wrap’是否指wrap(w)? - 为什么
i_ref和d_ref没有成为w中tuple成员的引用? - 如何实现符合预期行为的包装类?
问题解答
1. 报错含义解析
是的,报错中的wrap&对应代码里的auto&——结构化绑定的底层逻辑是将wrap(w)这个临时对象绑定到一个隐式的引用变量上,而非const左值引用无法绑定右值,wrap(w)是临时生成的右值对象,因此触发编译错误。这里的auto&指的是绑定整个wrap对象的隐式引用,而非i_ref或d_ref。
2. 未获得预期引用的原因
结构化绑定的类型推导规则决定了:
- 使用
auto [x, y] = expr时,会先将expr的结果拷贝/移动到一个隐式局部对象,再从该对象提取成员; - 你编写的
wrap的get()方法虽然返回原tuple成员的引用,但auto& [i_ref, d_ref] = wrap(w)尝试绑定的是整个临时wrap对象,而非直接绑定get()返回的引用,因此触发右值绑定左值引用的错误,自然无法得到预期的成员引用。
3. 符合预期的实现方案
修正后的Wrap类与特化
Wrap类的核心逻辑无需大幅修改,只需确保get()方法的返回类型正确对应原tuple成员的引用:
class widget { friend class wrap; std::tuple<int, double> m_tuple {1, 1.0}; }; class wrap { public: // 绑定左值widget,持有原tuple的引用 wrap(widget& f) : m_tuple(f.m_tuple) {} // 支持绑定右值widget(可选,根据业务需求) wrap(widget&& f) : m_tuple(f.m_tuple) {} // 左值Wrap对象返回左值引用 template<std::size_t Index> auto get() & -> std::tuple_element_t<Index, wrap>& { return std::get<Index>(m_tuple); } // 右值Wrap对象返回左值引用(指向原tuple成员) template<std::size_t Index> auto get() && -> std::tuple_element_t<Index, wrap>& { return std::get<Index>(m_tuple); } // const左值Wrap对象返回const引用 template<std::size_t Index> auto get() const& -> const std::tuple_element_t<Index, wrap>& { return std::get<Index>(m_tuple); } // const右值Wrap对象返回const引用 template<std::size_t Index> auto get() const&& -> const std::tuple_element_t<Index, wrap>& { return std::get<Index>(m_tuple); } private: std::tuple<int, double>& m_tuple; }; namespace std { template<> struct tuple_size<wrap> : tuple_size<std::tuple<int, double>> {}; template<size_t Index> struct tuple_element<Index, wrap> : tuple_element<Index, std::tuple<int, double>> {}; } // namespace std
正确的结构化绑定用法
方式1:先创建左值Wrap对象再绑定
int main() { widget w; // 获取副本:临时Wrap对象的get()返回引用,结构化绑定会拷贝该值到i_copy/d_copy auto [i_copy, d_copy] = wrap(w); i_copy = 2; d_copy = 2.0; // w.m_tuple 仍为 {1, 1.0} // 获取引用:先将Wrap对象存为左值,再绑定 wrap w_wrap(w); auto& [i_ref, d_ref] = w_wrap; i_ref = 2; d_ref = 2.0; // w.m_tuple 变为 {2, 2.0} }
方式2:使用auto&&绑定临时Wrap对象
auto&&可以绑定右值,此时结构化绑定会调用get() &&方法,直接获取原tuple成员的引用:
int main() { widget w; auto [i_copy, d_copy] = wrap(w); i_copy = 2; d_copy = 2.0; // auto&&绑定临时右值Wrap对象,i_ref/d_ref直接引用w.m_tuple的成员 auto&& [i_ref, d_ref] = wrap(w); i_ref = 2; d_ref = 2.0; }
原理说明
auto [i_copy, d_copy] = wrap(w):临时Wrap对象被构造(持有原tuple的引用),结构化绑定调用get()获取成员引用后,将值拷贝到i_copy和d_copy,因此修改副本不影响原widget;auto&& [i_ref, d_ref] = wrap(w):auto&&绑定临时Wrap右值,结构化绑定调用get() &&返回原tuple成员的引用,因此i_ref和d_ref直接关联原widget的tuple成员,修改会同步到原对象;- 如果使用
const auto& [x,y] = wrap(w),则会调用get() const&&返回const引用,无法修改原tuple成员。
内容的提问来源于stack exchange,提问作者red
相关产品推荐
相关产品推荐

