如何在C++17中以符合标准的方式交换两个std::tie调用的结果?
我一直想找符合C++标准的方法,让这段代码编译通过:
int a = 1; int b = 2; int c = 3; int d = 4; std::swap(std::tie(a, b), std::tie(c, d));
实际场景更复杂,和zip迭代器相关,但上面这段是问题核心。
这段代码编译失败的原因是:std::swap期望接收左值引用,而std::tie返回的是std::tuple<T&...>——理论上我们可以交换它间接引用的元素,哪怕这个tuple是右值。
我试过一种可行但不符合标准的方法:在std命名空间里添加std::swap的重载:
namespace std { template<typename... Refs> void swap(std::tuple<Refs&...> lhs, std::tuple<Refs&...> rhs) { lhs.swap(rhs); } }
但这种修改std命名空间的做法不符合C++标准。
另一种思路是自定义一个包装std::tuple的类型,在自己的命名空间里添加swap重载,这样用在std::sort这类标准算法时,能通过参数依赖查找(ADL)找到对应的swap:
template<typename... Ts> struct tuple_wrapper { std::tuple<Ts...> ts_; tuple_wrapper(Ts... ts) : ts_(ts...) {} // 其他实现... };
但编写std::tuple的透明包装难度极大——和std::sort配合时经常出现大量编译错误,而且模拟std::tuple的复杂类型完全不是理想方案。
有没有简单且符合标准的解决方案?任何建议都感激不尽!
更新1
我希望在C17中解决这个问题,感谢康桓瑋指出:由于P2321提案,这段代码在C23里可以直接运行。
更新2
以下是我用到的类的代码片段:
template<typename... InputIts> class zip_iterator { std::tuple<InputIts...> its_; public: using iterator_category = std::random_access_iterator_tag; using value_type = std::tuple<typename std::iterator_traits<InputIts>::value_type...>; using difference_type = std::common_type_t< typename std::iterator_traits<InputIts>::difference_type...>; using pointer = std::tuple<typename std::iterator_traits<InputIts>::pointer...>; using reference = std::tuple<typename std::iterator_traits<InputIts>::reference...>; zip_iterator(InputIts... its) : its_(its...) {} reference operator*() { return std::apply([](auto&... its) { return std::tie(*its...); }, its_); } // 其他运算符实现... };
我遇到的问题和reference类型有关,以及它和std::swap、std::sort的交互。
注:这个实现是参考一篇代码评审文章的思路,我只是想做个更简化的版本😅
后续发现
我发现这个问题比预想的复杂得多,涉及「代理迭代器」的相关内容。如果想了解完整的复杂性,可以参考Eric Niebler的四篇文章:
- 《To Be or Not to Be an Iterator》
- 《Iterators++ Part 1》
- 《Iterators++ Part 2》
- 《Iterators++ Part 3》
简而言之,要正确处理这类问题(比如支持仅移动类型等),需要修改标准库。目前最合理的建议是等待C++23,或者使用ranges-v3库🙈
内容的提问来源于stack exchange,提问作者Tom

