我已经尝试了一段时间寻找一种符合标准的方法来编译这样的东西:
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&...>
,您可以让它交换它间接引用的元素(即使 std::tuple
是右值)。
使其发挥作用的一种方法是将
std::swap
的重载添加到 std
命名空间:
namespace std {
template<typename... Refs>
void swap(std::tuple<Refs&...> lhs, std::tuple<Refs&...> rhs) {
lhs.swap(rhs);
}
}
但是,我知道这不符合标准......
另一种替代方法是添加一个本质上是
std::tuple
包装器的类型,并在您自己的命名空间中添加 swap
的重载。当从另一个标准算法(如 swap
)使用该类型时,可以通过参数相关查找 (ADL) 找到 std::sort
。
template<typename... Ts>
struct tuple_wrapper {
std::tuple<Ts...> ts_;
tuple_wrapper(Ts... ts) : ts_(ts...) {}
// implementation...
};
问题是尝试围绕
std::tuple
编写一个透明包装器,至少可以说是很棘手的 😅(如果我尝试将这种类型与 std::sort
一起使用,我经常会遇到编译错误页面并编写新的复杂代码)键入来模仿 std::tuple
似乎不太理想...)。
有人对如何使用符合标准的解决方案以简单的方式解决此问题有任何建议吗?任何想法将不胜感激。
谢谢!
更新
理想情况下,我想让它在 C++ 17 中工作,谢谢康桓玮指出,由于 P2321,它实际上在 C++ 23 中工作。
更新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_);
}
// ... rest of implementation, mostly other operators
};
我一直遇到的问题是
reference
,以及它如何与 std::swap
以及 std::sort
相互作用。
请注意,此实现的灵感来自于这篇文章以供参考 - https://codereview.stackexchange.com/questions/231352/c17-zip-iterator-completed-with-stdsort。我只是想看看是否可以创建一个稍微简单的实现(如果可能的话)😅
好吧,我有一个至少让我相当满意的解决方案。
非常感谢康桓玮、André、Alan Birtles、joergbrech 和 Jarod42 的评论,帮助我走向正确的方向(特别是引用了 P2321 和 C++ 23 解决方案的康桓玮)。
如果您使用的是 C++ 23,请立即停止阅读。这个问题已修复并且可以正常工作 TM。
如果您仍然无法使用 C++ 20 或 C++ 17,请继续阅读...
要解决此问题(并避免需要向
std
命名空间添加重载),您可以在 std::tuple
周围添加包装类型。
下面是几个几乎肯定不完整的选项(并且在某些用法下可能会失败),但目前它们对我有用。我将很快为此编写一系列测试和基准测试,并将链接到此处以供将来参考,希望有更多修复/改进。
有两种可能的选择,我还没有决定哪一个更可取(需要更多的调查/测试/分析),但我现在将它们都包含在这里。
创建
tuple_wrapper
类型并包含 tuple_wrapper
的隐式构造函数以从 std::tuple
进行转换。
namespace thh {
template <typename... Ts>
struct tuple_wrapper {
std::tuple<Ts...> ts_;
tuple_wrapper(Ts... ts) : ts_(ts...) {}
// implicit conversion from std::tuple
tuple_wrapper(std::tuple<Ts...> tup) : ts_(std::move(tup)) {}
};
// swap overload for tuple_wrapper of references
template <typename... Refs>
void swap(tuple_wrapper<Refs&...> lhs, tuple_wrapper<Refs&...> rhs) {
lhs.ts_.swap(rhs.ts_);
}
} // namespace thh
// usage
int a = 1;
int b = 2;
int c = 3;
int d = 4;
using std::swap;
swap(thh::tuple_wrapper(std::tie(a, b)),
thh::tuple_wrapper(std::tie(c, d)));
创建一个
tuple_wrapper
类型并提供我们自己的 std::tie
实现(请参阅参考实现此处。
namespace thh {
template <typename... Ts>
struct tuple_wrapper {
std::tuple<Ts...> ts_;
tuple_wrapper(Ts... ts) : ts_(ts...) {}
};
// our own version of tie that returns a tuple_wrapper
template <typename... Args>
constexpr tuple_wrapper<Args&...> tie(Args&... args) noexcept {
return {args...};
}
// swap overload for tuple_wrapper of references
template <typename... Refs>
void swap(tuple_wrapper<Refs&...> lhs, tuple_wrapper<Refs&...> rhs) {
lhs.ts_.swap(rhs.ts_);
}
} // namespace thh
// usage
int a = 1;
int b = 2;
int c = 3;
int d = 4;
using std::swap;
swap(thh::tie(a, b), thh::tie(c, d));
第一个也许更好?我还不确定是否有任何理由更喜欢其中一种(约定/兼容性/性能?)。我将做更多测试,如果找到更好的答案,我会报告。
以上希望能回答我最初的问题。我提到的稍微复杂的用例变得更加复杂(需要更多的操作员才能很好地使用
std::sort
)。
我当前的(可能非常不完整的实现)看起来像这样:
namespace thh {
template<typename... Ts>
struct tuple_wrapper {
std::tuple<Ts...> ts_;
tuple_wrapper(Ts... ts) : ts_(ts...) {}
tuple_wrapper(std::tuple<Ts...> tup) : ts_(std::move(tup)) {}
template<typename... UTypes>
tuple_wrapper& operator=(tuple_wrapper<UTypes...>&& other) {
ts_ = std::move(other).ts_;
return *this;
}
template<typename... UTypes>
tuple_wrapper& operator=(const tuple_wrapper<UTypes...>& other) {
ts_ = other.ts_;
return *this;
}
template<typename... UTypes>
tuple_wrapper(tuple_wrapper<UTypes...>&& other) {
ts_ = std::move(other).ts_;
}
template<typename... UTypes>
tuple_wrapper(const tuple_wrapper<UTypes...>& other) {
ts_ = other.ts_;
}
template<size_t I>
auto& get() {
return std::get<I>(ts_);
}
template<size_t I>
const auto& get() const {
return std::get<I>(ts_);
}
bool operator==(const tuple_wrapper& rhs) const { return ts_ == rhs.ts_; }
bool operator!=(const tuple_wrapper& rhs) const { return !(*this == rhs); }
};
} // namespace thh
我不会在此处粘贴所有代码,但您可以在下面找到
zip
迭代器的完整实现:
这个实现很大程度上受到了这个问题和 DarioP 的 Zip 实现的启发。请参阅 ZipIterator GitHub 项目以获取替代实现。
另外,对于一些额外的上下文,促使我们寻找更有效的方法来对多个范围进行排序,而不需要分配额外的内存。我之前使用的方法在这篇博文中有描述 - 间接排序。
再次感谢,我希望这对其他人也有用 - 一旦全部整理并测试完毕,我将使用此代码添加指向我的存储库的链接! 😅