std::views或range-v3是否有集合操作视图?若无该如何实现?
集合操作视图的存在性与实现方案
一、标准库与range-v3的支持情况
- C标准库(截至C23):没有直接提供这类集合操作视图。标准库中的
std::set_union、std::set_difference、std::set_intersection都是输出型算法,必须写入目标迭代器,无法直接生成惰性视图。 - range-v3库:完全支持你期望的功能,提供了
ranges::views::set_intersection、ranges::views::set_union、ranges::views::set_difference三个视图适配器,既支持直接调用views::set_xyz(a, b),也支持管道式语法a | views::set_xyz(b)。
二、自行实现思路(基于C++20 Ranges)
如果无法依赖range-v3,可基于C++20的视图规范,实现惰性求值的集合操作视图,核心是模拟标准集合算法的逻辑,避免提前生成完整结果。
1. 核心逻辑前提
所有集合操作视图要求输入范围已按升序排序,否则结果未定义。视图会惰性遍历两个输入范围,根据元素大小关系决定是否输出当前元素,并移动对应迭代器。
2. 示例实现:set_intersection视图
以下是完整的可运行实现,同时支持直接调用和管道语法:
#include <ranges> #include <iterator> #include <concepts> namespace views { // 核心逻辑实现:处理两个已排序范围的交集 namespace detail { template<std::ranges::input_range R1, std::ranges::input_range R2> struct intersection_view : std::ranges::view_interface<intersection_view<R1, R2>> { R1 r1_; R2 r2_; intersection_view(R1 r1, R2 r2) : r1_(std::move(r1)), r2_(std::move(r2)) {} struct iterator { using value_type = std::common_type_t<std::ranges::range_value_t<R1>, std::ranges::range_value_t<R2>>; using iterator_category = std::input_iterator_tag; using reference = const value_type&; using pointer = const value_type*; std::ranges::iterator_t<R1> it1_, end1_; std::ranges::iterator_t<R2> it2_, end2_; iterator(std::ranges::iterator_t<R1> it1, std::ranges::iterator_t<R1> end1, std::ranges::iterator_t<R2> it2, std::ranges::iterator_t<R2> end2) : it1_(it1), end1_(end1), it2_(it2), end2_(end2) { advance_to_match(); } void advance_to_match() { while (it1_ != end1_ && it2_ != end2_) { if (*it1_ < *it2_) ++it1_; else if (*it2_ < *it1_) ++it2_; else break; } } reference operator*() const { return *it1_; } pointer operator->() const { return std::addressof(*it1_); } iterator& operator++() { ++it1_; ++it2_; advance_to_match(); return *this; } iterator operator++(int) { auto tmp = *this; ++*this; return tmp; } friend bool operator==(const iterator& lhs, const iterator& rhs) { return lhs.it1_ == rhs.it1_ && lhs.it2_ == rhs.it2_; } }; iterator begin() { return {std::ranges::begin(r1_), std::ranges::end(r1_), std::ranges::begin(r2_), std::ranges::end(r2_)}; } iterator end() { return {std::ranges::end(r1_), std::ranges::end(r1_), std::ranges::end(r2_), std::ranges::end(r2_)}; } }; } // 适配器:支持直接调用和管道语法 inline constexpr auto set_intersection = []<std::ranges::input_range R>(R&& r) { struct adaptor { R r_; explicit adaptor(R&& r) : r_(std::forward<R>(r)) {} template<std::ranges::input_range Other> friend auto operator|(Other&& other, adaptor&& self) { static_assert(std::ranges::sorted<Other>); static_assert(std::ranges::sorted<R>); return detail::intersection_view(std::forward<Other>(other), std::forward<R>(self.r_)); } }; return adaptor(std::forward<R>(r)); }; // 重载:支持直接传入两个范围 template<std::ranges::input_range R1, std::ranges::input_range R2> auto set_intersection(R1&& r1, R2&& r2) { static_assert(std::ranges::sorted<R1>); static_assert(std::ranges::sorted<R2>); return detail::intersection_view(std::forward<R1>(r1), std::forward<R2>(r2)); } } // 测试代码 #include <iostream> int main() { int a[] {1, 2, 4, 8, 16}; int b[] {1, 8, 80}; // 直接调用 auto i = views::set_intersection(a, b); for (int x : i) std::cout << x << ' '; // 输出 1 8 std::cout << '\n'; // 管道语法 auto i_pipe = a | views::set_intersection(b); for (int x : i_pipe) std::cout << x << ' '; // 输出 1 8 std::cout << '\n'; return 0; }
3. 扩展到set_union和set_difference
按照相同框架,只需修改迭代器中的advance_to_match和operator++逻辑:
- set_union:每次输出较小的元素,若元素相等则仅输出一次并同时移动两个迭代器;
- set_difference:仅输出第一个范围中存在、第二个范围中不存在的元素,逻辑为:第一个元素小于第二个时输出并移动第一个迭代器,相等时同时移动两个迭代器,否则移动第二个迭代器。
三、关键注意事项
- 必须确保输入范围是已排序的,否则视图行为未定义;
- 实现需遵循C++20视图规范,保证惰性求值、可复制、满足
view概念; - 可扩展支持自定义比较器,在适配器中添加比较器参数即可,类似标准库算法的重载。
内容的提问来源于stack exchange,提问作者Jan Schultke
相关产品推荐
相关产品推荐

