| Document number | P3741R1 |
| Date | 2026-06-30 |
| Audience | LEWG, SG9 (Ranges) |
| Reply-to | Hewill Kang <hewillk@gmail.com> |
views::set_operations
This paper proposes four range adaptors for set operations rated as
Tier 3 in P2760, namely
views::set_difference,
views::set_intersection,
views::set_union, and views::set_symmetric_difference,
to extend the breadth of
Ranges.
These adaptors complement existing set algorithms by enabling composable, lazy, and allocation-free views for common set operations. They fill a notable gap in the Ranges library for many practical applications.
Initial revision.
1. Support custom comparator.
2. Provide reserve_hint member.
3. Support variadic template version.
4. Specify EB (Erroneous Behavior) when the range is not sorted.
Although there are corresponding constrained algorithm versions of set operations, they all need to output the results to some sort of output range. This brings advantages of the view's lazy evaluation: we can construct set elements on the fly without allocating memory in advance.
Given that set operations are extremely common in the real world, introducing corresponding range adaptors is valuable and facilitates the user experience with Ranges:
/* algorithm approach */
std::vector<int> dest;
ranges::set_union(v1, v2, std::back_inserter(dest));
/* no allocation, composable, n-ary */
auto union = views::set_union(v1, v2, v3, ...);
The union and intersection operations support variadic templates, which requires them to be implemented as Customization Point Objects (CPOs) rather than range adaptors.
This design inherently precludes pipe syntax. Even if they were adaptors, pipe syntax would create ambiguity. For
instance, in an expression like views::set_union(r2, r3), it is unclear
whether views::set_union(r2, r3) is intended to be a range adaptor expecting r1 as input,
or a view factory that
computes the union of r2 and r3.
We have also decided against pipe syntax for other two. Set
operations generally
require sorted ranges; since a range passed through a pipe is rarely guaranteed to be sorted, users would
likely need to sort it manually first.
Given that we currently lack eager algorithms like action::sort to
facilitate this within a pipeline,
the utility of pipe syntax for these operations is limited. We believe that maintaining a consistent API design
across all set operations is preferable.
For
views::set_operations(A, B), both intersection and difference produce only elements from A, so the
result is just a subset of A;
the element type of B is irrelevant for output, only its order matters. The only thing that matters is making sure
the elements of both
ranges are strictly weakly ordered so that they can be compared meaningfully.
The standard already has a
concept for this, namely indirect_strict_weak_order, which is also
used for the corresponding constrained algorithm (a component of mergeable
concept).
The signatures of
the
two
classes would be:
template<view V1, view V2>
requires input_range<V1> && input_range<V2> &&
indirect_strict_weak_order<ranges::less, iterator_t<V1>, iterator_t<V2>>
class set_meow_view;
Unlike the above, union and symmetric difference produce elements from both A and B. In addition to ensuring that both ranges are strictly weakly ordered, we also need to ensure that the element types of both ranges are somehow compatible.
Thanks to the fact that it is already concat_view in the standard,
the concatable concept is perfect for such a purpose. The signatures of
both
would be:
template<view V1, view V2>
requires input_range<V1> && input_range<V2> &&
indirect_strict_weak_order<ranges::less, iterator_t<V1>, iterator_t<V2>> &&
concatable<V1, V2>
class set_meow_view;
The iterators of the four views all follow a similar design. When they are constructed, we first find the next valid
element through the satisfy() function, and then in each operator++(), we increment
the
underlying iterator and
call the satisfy() again to find the next valid element, and so on.
Since in satisfy(), we also need to check whether the iterators of A or B
have reached the end
to determine the next valid element, we need to know the information of the sentinels of both, which means we
need to store both sentinels in the iterator.
For set_intersection_view, its iterator signature is as follows:
class set_intersection_view::iterator {
iterator_t<V1> current1_;
sentinel_t<V1> end1_;
iterator_t<V2> current2_;
sentinel_t<V2> end2_;
constexpr void
satisfy() {
while (current1_ != end1_ && current2_ != end2_) {
/* Find the next valid element in the first range */
}
}
constexpr iterator(iterator_t<V1> current1, sentinel_t<V1> end1,
iterator_t<V2> current2, sentinel_t<V2> end2)
: current1_(std::move(current1)), end1_(end1),
current2_(std::move(current2)), end2_(end2) {
satisfy();
}
public:
constexpr decltype(auto) operator*() const { return *current1_; }
constexpr iterator&
operator++() {
++current1_;
++current2_;
satisfy();
return *this;
}
friend constexpr bool
operator==(const iterator& x, default_sentinel_t) {
return x.current1_ == x.end1_ || x.current2_ == x.end2_;
}
};
We can slightly modify the logic of the three functions above, satisfy(),
operator++(), and operator==(), to make a
corresponding iterator for
set_difference_view:
class set_difference_view::iterator {
iterator_t<V1> current1_;
sentinel_t<V1> end1_;
iterator_t<V2> current2_;
sentinel_t<V2> end2_;
constexpr void
satisfy() {
while (current1_ != end1_ && current2_ != end2_) {
/* New condition to find the next valid element in the first range */
}
}
constexpr iterator(iterator_t<V1> current1, sentinel_t<V1> end1,
iterator_t<V2> current2, sentinel_t<V2> end2)
: current1_(std::move(current1)), end1_(end1),
current2_(std::move(current2)), end2_(end2) {
satisfy();
}
public:
constexpr decltype(auto) operator*() const { return *current1_; }
constexpr iterator&
operator++() {
++current1_;
/* ++current2_; */
satisfy();
return *this;
}
friend constexpr bool
operator==(const iterator& x, default_sentinel_t) {
return x.current1_ == x.end1_ /* || x.current2_ == x.end2_*/;
}
};
For set_union_view's iterator, it is necessary to know which underlying iterator
is
active right now,
so an additional flag is need to indicate that.
In addition, since the resulting set contains elements from two different ranges, the new reference type needs to be
a common reference of the two, in which case concat-reference-t nicely fits
the
purpose:
class set_union_view::iterator {
iterator_t<V1> current1_;
sentinel_t<V1> end1_;
iterator_t<V2> current2_;
sentinel_t<V2> end2_;
size_t active_idx_;
constexpr void
satisfy() {
/* Find the next valid element from two ranges */
}
constexpr iterator(iterator_t<V1> current1, sentinel_t<V1> end1,
iterator_t<V2> current2, sentinel_t<V2> end2)
: current1_(std::move(current1)), end1_(end1),
current2_(std::move(current2)), end2_(end2) {
satisfy();
}
public:
constexpr concat-reference-t<V1, V2>
operator*() const {
if (active_idx_ == 0)
return *current1_;
return *current2_;
}
constexpr iterator&
operator++() {
if (active_idx_ == 0)
++current1_;
else
++current2_;
satisfy();
return *this;
}
friend constexpr bool
operator==(const iterator& x, default_sentinel_t) {
return x.current1_ == x.end1_ && x.current2_ == x.end2_;
}
};
Similarly, we can make set_symmetric_difference_view's iterator by modifying
satisfy() to skip the invalid part.
During the Brno, SG9 suggested exploring variadic template versions for these views to increase flexibility. However, after further technical consideration, we believe that only intersection and union are actually worth having variadic template versions because they are inherently less ambiguous. Making the other two into variadic templates is neither reasonable nor worthwhile.
The reason only first two are viable comes down to their mathematical properties.
Both are strictly associative and commutative.
For instance, an expression like (A ∪ B) ∪ C yields the exact same result as A ∪ (B ∪ C). Under multiset semantics, evaluating the first grouping yields a final
element frequency of max(max(a, b), c), while the second yields max(a, max(b, c)). Since the max operation is associative, both groupings produce an
identical range.
The exact same logic applies to intersection, where an expression like
(A ∩ B) ∩ C is identical to A ∩ (B ∩ C). The
final frequency for both groupings is a deterministic min(min(a, b), c) = min(a, min(b, c)). Because both operations yield identical element
frequencies and order, a variadic version eliminates ambiguity around evaluation order.
Note that under the variadic mathematical definition, a unary set_union or set_intersection simply yields the range itself.
In stark contrast, set difference is non-associative. An expression like (A \ B) \ C yields a completely different result than A \ (B \ C), because the former removes elements found in either B or C from A, while
the latter removes elements from A that are in B but not in C. A variadic API would force users to guess which
evaluation order the library implements.
Similarly, while symmetric difference is associative,meaning (A Δ B) Δ C does
equal A Δ (B Δ C), adding a variadic version just is not worth the implementation
complexity. In a multi-range context, a variadic symmetric difference mathematically evaluates to elements that
appear an odd number of times. This is a highly niche mathematical artifact; in real-world engineering, developers
are usually looking for elements that appear exactly once across all ranges, making this behavior counter-intuitive
and practically useless.
For the variadic version of set_union, a pairwise constraint is strictly
required because the output contains elements from all ranges. Any element may need to be compared with any other in
downstream operations:
// Pack... must be pairwise compatible
template<view... Vs>
requires (input_range<Vs> && ...) &&
(indirect_strict_weak_order<ranges::less, iterator_t<Vi>, iterator_t<Vj>> /* for every i, j */) &&
concatable<Vs...>
class set_union_view;
For set_intersection, the implementation technically only requires comparing the
first range V1 against all subsequent ranges, since the output is strictly a
subset of V1:
// Implementation minimum requirement
template<view V1, view... Vs>
requires input_range<V1> && (input_range<Vs> && ...) &&
(indirect_strict_weak_order<ranges::less, iterator_t<V1>, iterator_t<Vs>> && ...)
class set_intersection_view;
However, from a theoretical standpoint, intersection is fundamentally commutative and associative, meaning that a
valid relationship exists between all elements regardless of their order. To respect this mathematical property,
set_intersection should adopt the same pairwise constraint as set_union.
This also avoid confusing behavior in practice. Without pairwise constraints, an expression like set_intersection(A, B, C) might compile, but changing it to set_intersection(B, A, C) would suddenly fail just because B and C cannot be compared
directly. Ordering the arguments differently shouldn't break the build, so aligning the constraints ensures
consistent behavior.
While range/v3 supports both custom comparisons and projections like views::set_operations(rng1, rng2, pred, proj1, proj2), we believe only supporting a
custom comparison
is reasonable, since several views like views::filter or
views::chunk_by already take custom predicates.
On the other hand, supporting custom projections does not really fit the direction of the Ranges
library, as no range adaptor supports them. This feature offers little practical usability improvement,
considering users can just use views::transform to achieve equivalent behavior.
Instead, adding
projections forces the view class to store extra fields for the projections, which introduces a heavyweight cost.
Furthermore, supporting projections gets ugly once we move to variadic templates, which would end up with a messy
interface
like
views::set_operations(pred, proj1, proj2, proj3..., r1, r2, r3...). Nobody wants
to write or read that.
Supporting a custom comparison also impacts whether these views can be borrowed ranges. If a custom
comparison object is provided, it must be stored within the view class
itself. Consequently, the iterator needs to maintain a pointer back to its parent view to access this comparator.
Because the iterator holds a dependency on the view's member, these views cannot model borrowed_range .
According to P3117R1
Extending
Conditionally Borrowed, if the comparator is a tiny-func, the iterator does
not need a parent
pointer because
it can construct the comparator on the fly. This theoretically allows us to support borrowed_range.
However, without a parent pointer, the iterator must independently store the end
iterators
of all underlying ranges to safely drive its traversal logic. For non-variadic ones, this requires storing
2 extra end iterators inside the iterator class. For a variadic version, the iterator would have to carry a whole
collection of end
iterators. This massive size increase makes the iterator too heavyweight, meaning supporting borrowed_range this way is simply not worth the cost.
The fundamental reason these views cannot universally support common_range is an
implementation impossibility; the exact end positions of the
underlying iterators cannot be known in advance. For example, set_difference(r1, r2) finishes the moment r1
reaches its end. But at that exact moment, where should the iterator for r2 be
pointing? Its final position depends entirely on the runtime data.
The same fundamental issue applies to set_intersection. The traversal stops as
soon as any underlying range hits its end, meaning we can never predict which one will exhaust first, nor where the
remaining iterators will be sitting at that moment.
On the other hand, set_union and set_symmetric_difference require fully
exhausting all participating ranges to complete their traversal, providing common_range support specifically for these two views is feasible.
The paper does currenly not to provide any to keep the design simple and consistent.
Theoretically, these views could support bidirectional iteration, but doing so provides little value. For set_difference and set_intersection, it is only
known that one range has reached its end, while the final positions of the other ranges remain unknown.
Consequently, these views cannot model common_range, which makes supporting bidirectional_range meaningless because it is impossible to execute --view.end().
For set_union and set_symmetric_difference,
reverse traversal cannot be correctly supported without tracking the traversal history when the underlying ranges
contain duplicate elements:
// Input ranges
{ 2, 3, [4] }
{ 2, 3, (4), <4> }
// Forward Traversal for set_union:
{ 2, 3, [4], <4> }
// Final state: r1_it == r1.end(), r2_it == r2.end()
// Reverse Traversal from set_union:
{ [4], (4), 3, 2 } // <-- Wrong elements
It is also worth noting that supporting bidirectional iteration would introduce potential use-after-move issues due
to element comparisons between the two ranges. For instance, composing views::as_rvalue | views::reverse could lead to undefined behavior.
Therefore, the author does not plan to support bidirectional iteration, which is consistent with the design of range/v3.
const-iterable (even for pure input-range)
Except for set union, satisfy() for other operations has the worst
complexity
of O(n) because
we need to skip the invalid white area to locate the next valid element. In order to ensure the amortized constant
time
complexity of
begin() required by the range concept, we need
to
cache the
iterator in the first call to begin(), which means that other view classes except
set_union_view are
not const-iterable.
It is worth noting that while C++26 introduced const-iterable support for filter_view when the underlying range is an pure input range, a similar optimization
is unnecessary for our views. Although input-only ranges do not require iterator caching to maintain complexity
guarantees, all set operations fundamentally require their input sequences to be sorted.
Since pre-sorted sequence in an input-only context is exceedingly rare in practical scenarios, adding extra
implementation complexity to support a non-caching const begin() for this niche
edge case provides no real-world motivation. Therefore, the author chose not to provide such support, keeping the
design pragmatic and clean.
Here is the summary table:
| View | const-Iterable |
Caches begin() |
Complexity |
|---|---|---|---|
set_difference_view |
❌ | ✅ | Amortized constant |
set_intersection_view |
❌ | ✅ | Amortized constant |
set_union_view |
✅ | ❌ | Constant |
set_symmetric_difference_view |
❌ | ✅ | Amortized constant |
reserve_hint() member
During the Brno meeting, SG9 suggested that providing a reserve_hint member
function would still be valuable, even if it only offers a loose upper bound. The primary motivation is to allow
downstream operations, such as ranges::to<std::vector>(), to pre-allocate
memory and reduce the overhead of frequent reallocations.
The implementation logic for computing these upper bounds is straightforward. For a union or a symmetric difference,
the maximum possible size is simply the sum of both range sizes, or size(A) + size(B). For an intersection, the resulting size can never exceed the size
of the smaller range, which is std::min(size(A), size(B)). Lastly, for a set
difference, the size of the output is strictly bounded by the size of the first range, size(A).
iter_swap() specializations
It doesn't make sense to provide iter_swap specializations for these new
iterators,
since we'd
break the origin
order by swapping the elements which leads to undefined behavior.
Following feedback from SG9, violating the sorted precondition for input ranges is designated as Erroneous Behavior rather than Undefined Behavior.
While set operations require both input ranges to be sorted to ensure correct semantics, treating unsorted inputs as erroneous behavior strikes the right balance, which allows implementations to catch these contract violations.
base() member
intersection and difference can be seen as a subset of the first range,
so it makes sense to provide base() members for it and its iterator to access the
first
underlying view and iterator.
The author implemented four views::set_operationss based on libstdc++, see
godbolt.
This wording is relative to the latest working draft.
Add one new feature-test macro to 17.3.2 [version.syn]:
#define __cpp_lib_ranges_set_view 20XXXXL // freestanding, also in <ranges>
Modify 25.2 [ranges.syn], Header <ranges> synopsis, as indicated:
#include <compare> // see [compare.syn] #include <initializer_list> // see [initializer.list.syn] #include <iterator> // see [iterator.synopsis] namespace std::ranges { […] namespace views { inline constexpr unspecified to_input = unspecified; } // [range.set.intersection], set intersection view template<class Comp, input_range... Views> requires see below class set_intersection_view; namespace views { inline constexpr unspecified set_intersection = unspecified; } // [range.set.union], set union view template<class Comp, input_range... Views> requires see below class set_union_view; namespace views { inline constexpr unspecified set_union = unspecified; } // [range.set.difference], set difference view template<class Comp, input_range V1, input_range V2> requires see below class set_difference_view; namespace views { inline constexpr unspecified set_difference = unspecified; } // [range.set.symmetric.difference], set symmetric difference view template<class Comp, input_range V1, input_range V2> requires see below class set_symmetric_difference_view; namespace views { inline constexpr unspecified set_symmetric_difference = unspecified; } }
Add 25.7.? Set intersection view [range.set.intersection] as indicated:
[25.7.?.1] Overview [range.set.intersection.overview]
-1- set_intersection_view presents a view of set intersection between
sorted ranges.
-2-
The name views::set_intersection denotes a customization point object
[customization.point.object].
Let F be a subexpression,
let FD be decay_t<decltype((F))>,
and let Es... be a pack of subexpressions, the
expression views::set_intersection(F, Es...) is
expression-equivalent to set_intersection_view(ranges::less{}, F, Es...)
if FD models range,
and set_intersection_view(F, Es...) otherwise.
-3- [Example 1:
vector v1{1, 2, 2, 3, 4, 5, 6};
vector v2{ 2, 2, 3, 3, 5, 7};
println("{}", views::set_intersection(v1, v2)); // prints [2 2 3 5]
— end example]
[25.7.?.2] Class template set_intersection_view
[range.set.intersection.view]
namespace std::ranges {
template<class F, class... Rs>
constexpr bool pairwise-indirect-strict-weak-order =
(indirect_strict_weak_order<F, iterator_t<Rs>> && ...); // exposition only
template<class F, class R, class... Rs> requires(sizeof...(Rs) > 0)
constexpr bool pairwise-indirect-strict-weak-order<F, R, Rs...> = // exposition only
(indirect_strict_weak_order<F, iterator_t<R>, iterator_t<Rs>> && ...) &&
pairwise-indirect-strict-weak-order<F, Rs...>;
template<class Comp, input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0) && is_object_v<Comp> &&
pairwise-indirect-strict-weak-order<Comp, Views...>
class set_intersection_view : public view_interface<set_intersection_view<Comp, Views...>> {
movable-box<Comp> comp_; // exposition only
tuple<Views...> views_; // exposition only
// [range.set.intersection.iterator], class set_intersection_view::iterator
class iterator; // exposition only
public:
set_intersection_view() = default;
constexpr explicit set_intersection_view(Comp comp, Views... bases);
constexpr Views...[0] base() const& requires copy_constructible<Views...[0]>
{ return std::get<0>(views_); }
constexpr Views...[0] base() && { return std::move(std::get<0>(views_)); }
constexpr iterator begin();
constexpr default_sentinel_t end() const noexcept { return default_sentinel; }
constexpr auto reserve_hint() requires (approximately_sized_range<Views> && ...);
constexpr auto reserve_hint() const requires (approximately_sized_range<const Views> && ...);
};
template<class Comp, class... Rs>
set_intersection_view(Comp, Rs&&...)
-> set_intersection_view<Comp, views::all_t<Rs>...>;
}
constexpr explicit set_intersection_view(Comp comp, Views... bases);
-1- Effects: Initializes
comp_withstd::move(comp)andviews_withstd::move(bases).... The behavior is erroneous if any range in the packbases...is not sorted with respect tocomp.
constexpr iterator begin();
-2- Returns:
iterator(this, tuple-transform(ranges::begin, views_));-3- Remarks: In order to provide the amortized constant time complexity required by the
rangeconcept whenset_intersection_viewmodelsforward_range, this function caches the result within theset_intersection_viewfor use on subsequent calls.
constexpr auto reserve_hint() requires (approximately_sized_range<Views> && ...);
constexpr auto reserve_hint() const requires (approximately_sized_range<const Views> && ...);
-3- Effects: Equivalent to:
return apply([](auto... sizes) { using CT = make-unsigned-like-t<common_type_t<decltype(sizes)...>>; return ranges::min({CT(sizes)...}); }, tuple-transform(ranges::reserve_hint, views_));
[25.7.?.2] Class set_intersection_view::iterator
[range.set.intersection.iterator]
namespace std::ranges {
template<class Comp, input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0) && is_object_v<Comp> &&
pairwise-indirect-strict-weak-order<Comp, Views...>
class set_intersection_view<Comp, Views...>::iterator {
tuple<iterator_t<Views>...> current_; // exposition only
set_intersection_view* parent_ = nullptr; // exposition only
template<size_t N = 1>
constexpr bool satisfy-next(); // exposition only
constexpr void satisfy(); // exposition only
constexpr explicit iterator(set_intersection_view* parent,
tuple<iterator_t<Views>...> current); // exposition only
public:
using iterator_category = see below; // not always present
using iterator_concept =
conditional_t<(forward_range<Views> && ...), forward_iterator_tag, input_iterator_tag>;
using value_type = range_value_t<Views...[0]>;
using difference_type = range_difference_t<Views...[0]>;
iterator() = default;
constexpr iterator_t<Views...[0]> base() && { return std::move(std::get<0>(current_)); }
constexpr const iterator_t<Views...[0]>&
base() const& noexcept { return std::get<0>(current_); }
constexpr range_reference_t<Views...[0]> operator*() const { return *std::get<0>(current_); }
constexpr iterator& operator++();
constexpr void operator++(int);
constexpr iterator operator++(int) requires (forward_range<Views> && ...) = default;
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires (equality_comparable<iterator_t<Views>> && ...);
friend constexpr bool operator==(const iterator& x, default_sentinel_t);
friend constexpr range_rvalue_reference_t<Views...[0]> iter_move(const iterator& i)
noexcept(noexcept(ranges::iter_move(std::get<0>(i.current_))));
};
}
-1- The member typedef-name
iterator_categoryis defined if and only if(forward_range<Views> && ...)istrue. In that case,iterator::iterator_categoryis defined as follows: letCsdenote the pack of typesiterator_traits<iterator_t<Views>>::iterator_category....
(1.1.2) — If
(derived_from<Cs, forward_iterator_tag> && ...)istrue,iterator_categorydenotesforward_iterator_tag.(1.1.3) — Otherwise,
iterator_categorydenotesinput_iterator_tag.
template<size_t N = 1>
constexpr bool satisfy-next();
-2- Effects: Equivalent to:
if constexpr (N == sizeof...(Views)) return true; else { auto& first = std::get<0>(current_); if (first == ranges::end(std::get<0>(parent_->views_))) return true; auto& other = std::get<N>(current_); while (true) { if (other == ranges::end(std::get<N>(parent_->views_))) return true; if (invoke(*parent_->comp_, *first, *other)) { ++first; return false; } if (invoke(*parent_->comp_, *other, *first)) ++other; else break; } return satisfy-next<N + 1>(); }
constexpr void satisfy();
-3- Effects: Equivalent to:
[Note 1:while (!satisfy-next()) ;set_intersection_viewiterators use thesatisfyfunction to find the next valid element in the first range.— end note]
constexpr explicit iterator(set_intersection_view* parent, tuple<iterator_t<Views>...> current);
-4- Effects: Initializes
parent_withparentandcurrent_withstd::move(current); then callssatisfy().
constexpr iterator& operator++();
-5- Effects: Equivalent to:
tuple-for-each([](auto& i) { ++i; }, current_); satisfy(); return *this;
constexpr void operator++(int);
-6- Effects: Equivalent to
++*this.
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires (equality_comparable<iterator_t<Views>> && ...);
-7- Effects: Equivalent to:
return x.current_ == y.current_;
friend constexpr bool operator==(const iterator& x, default_sentinel_t);
8- Returns:
trueif there exists an integer0 ≤ i < sizeof...(Views)such thatbool(std::get<i>(x.current_) == ranges::end(std::get<i>(x.parent_->views_)))istrue. Otherwise,false.
friend constexpr range_rvalue_reference_t<Views...[0]> iter_move(const iterator& i)
noexcept(noexcept(ranges::iter_move(std::get<0>(i.current_))));
-9- Effects: Equivalent to:
return ranges::iter_move(std::get<0>(i.current_));
Add 25.7.? Set union view [range.set.union] as indicated:
[25.7.?.1] Overview [range.set.union.overview]
-1- set_union_view presents a view of set union between
sorted ranges.
-2-
The name views::set_union denotes a customization point object
[customization.point.object].
Let F be a subexpression,
let FD be decay_t<decltype((F))>,
and let Es... be a pack of subexpressions, the
expression views::set_union(F, Es...) is
expression-equivalent to set_union_view(ranges::less{}, F, Es...)
if FD models range,
and set_union_view(F, Es...) otherwise.
-3- [Example 1:
vector v1{1, 2, 3, 4, 5};
vector v2{ 3, 4, 5, 6, 7};
println("{}", views::set_union(v1, v2)); // prints [1 2 3 4 5 6 7]
— end example]
[25.7.?.2] Class template set_union_view
[range.set.union.view]
namespace std::ranges {
template<class Comp, input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0) && is_object_v<Comp> &&
pairwise-indirect-strict-weak-order<Comp, Views...> &&
concatable<Views...>
class set_union_view : public view_interface<set_union_view<Comp, Views...>> {
movable-box<Comp> comp_; // exposition only
tuple<Views...> views_; // exposition only
// [range.set.union.iterator], class template set_union_view::iterator
template<bool> class iterator; // exposition only
public:
set_union_view() = default;
constexpr explicit set_union_view(Comp comp, Views... bases);
constexpr iterator<false> begin();
constexpr iterator<true> begin() const
requires (range<const Views> && ...) &&
pairwise-indirect-strict-weak-order<const Comp, const Views...> &&
concatable<const Views...>;
constexpr default_sentinel_t end() const noexcept { return default_sentinel; }
constexpr auto reserve_hint() requires (approximately_sized_range<Views> && ...);
constexpr auto reserve_hint() const requires (approximately_sized_range<const Views> && ...);
};
template<class Comp, class... Rs>
set_union_view(Comp, Rs&&...)
-> set_union_view<Comp, views::all_t<Rs>...>;
}
constexpr explicit set_union_view(Comp comp, Views... bases);
-1- Effects: Initializes
comp_withstd::move(comp)andviews_withstd::move(bases).... The behavior is erroneous if any range in the packbases...is not sorted with respect tocomp.
constexpr iterator<false> begin();
-2- Returns:
iterator<false>(this, tuple-transform(ranges::begin, views_));
constexpr iterator<true> begin() const
requires (range<const Views> && ...) &&
pairwise_indirect_strict_weak_order<const Comp, const Views...> &&
concatable<const Views...>;
-3- Returns:
iterator<true>(this, tuple-transform(ranges::begin, views_));
constexpr auto reserve_hint() requires (approximately_sized_range<Views> && ...);
constexpr auto reserve_hint() const requires (approximately_sized_range<const Views> && ...);
-4- Effects: Equivalent to:
return apply( [](auto... sizes) { using CT = make-unsigned-like-t<common_type_t<decltype(sizes)...>>; return (CT(sizes) + ...); }, tuple-transform(ranges::reserve_hint, views_));
[25.7.?.2] Class template set_union_view::iterator
[range.set.union.iterator]
namespace std::ranges {
template<class Comp, input_range... Views>
requires (view<Views> && ...) && (sizeof...(Views) > 0) && is_object_v<Comp> &&
pairwise-indirect-strict-weak-order<Comp, Views...> &&
concatable<Views...>
template<bool Const>
class set_union_view<Comp, Views...>::iterator {
static constexpr size_t inactive = -1; // exposition only
tuple<iterator_t<maybe-const<Const, Views>>...> current_; // exposition only
maybe-const<Const, set_union_view>* parent_ = nullptr; // exposition only
size_t active_idx_ = inactive; // exposition only
constexpr void satisfy(); // exposition only
constexpr iterator(maybe-const<Const, set_union_view>* parent, // exposition only
tuple<iterator_t<maybe-const<Const, Views>>...> current);
public:
using iterator_category = see below; // not always present
using iterator_concept =
conditional_t<(forward_range<maybe-const<Const, Views>> && ...),
forward_iterator_tag, input_iterator_tag>;
using value_type = concat-value-t<maybe-const<Const, Views>...>;
using difference_type =
common_type_t<range_difference_t<maybe-const<Const, Views>>...>;
iterator() = default;
constexpr iterator(iterator<!Const> i)
requires Const && (convertible_to<iterator_t<Views>, iterator_t<const Views>> && ...);
constexpr concat-reference-t<maybe-const<Const, Views>...> operator*() const;
constexpr iterator& operator++();
constexpr void operator++(int);
constexpr iterator operator++(int) requires (forward_range<maybe-const<Const, Views>> && ...) = default;
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires (equality_comparable<iterator_t<maybe-const<Const, Views>>> && ...);
friend constexpr bool operator==(const iterator& x, default_sentinel_t) noexcept;
friend constexpr concat-rvalue-reference-t<maybe-const<Const, Views>...> iter_move(const iterator& i)
noexcept(see below);
};
}
-1- The member typedef-name
iterator_categoryis defined if and only ifall-forward<Const, Views ...>is modeled. In that case,iterator::iterator_categoryis defined as follows: letCsdenote the pack of typesiterator_traits<iterator_t<maybe-const<Const, Views>>>::iterator_category....
(1.1.2) — If
(derived_from<Cs, forward_iterator_tag> && ...)istrue,iterator_categorydenotesforward_iterator_tag.(1.1.3) — Otherwise,
iterator_categorydenotesinput_iterator_tag.
constexpr void satisfy();
-2- Effects:
[Note 1:
(2.1) — If there is no integer
iin the range [0, sizeof...(Views)) such thatbool(std::get<i>(current_) != ranges::end(std::get<i>(parent_->views_)))istrue, assignsinactivetoactive_idx_.(2.2) — Otherwise, assigns to
active_idx_the least integeriin the range [0, sizeof...(Views)) such thatbool(std::get<i>(current_) != ranges::end(std::get<i>(parent_->views_)))istrueand, for every integerjin the range [0, sizeof...(Views)) such thatj != iandbool(std::get<j>(current_) != ranges::end(std::get<j>(parent_->views_)))istrue,bool(invoke(*parent_->comp_, *std::get<j>(current_), *std::get<i>(current_)))isfalse.set_union_viewiterators use thesatisfyfunction to find the next valid element in two ranges.— end note]
constexpr iterator(maybe-const<Const, set_union_view>* parent,
tuple<iterator_t<maybe-const<Const, Views>>...> current);
-3- Effects: Initializes
parent_withparentandcurrent_withstd::move(current); then callssatisfy().
constexpr iterator(iterator<!Const> i)
requires Const && (convertible_to<iterator_t<Views>, iterator_t<const Views>> && ...);
-4- Effects: Initializes
parent_withi.parent_,current_withstd::move(i.current_), andactive_idx_withi.active_idx_.
constexpr concat-reference-t<maybe-const<Const, Views>...> operator*() const;
-5- Preconditions:
active_idx_ != inactiveistrue.-6- Effects: Let
ibeactive_idx_. Equivalent to:return *std::get<i>(current_);
constexpr iterator& operator++();
-7- Preconditions:
active_idx_ != inactiveistrue.-8- Effects: Let
ibeactive_idx_. For every integerjin the range [0, sizeof...(Views)) such thatj != iandbool(std::get<j>(current_) != ranges::end(std::get<j>(parent_->views_)))istrue, incrementsstd::get<j>(current_)if bothbool(invoke(*parent_->comp_, *std::get<j>(current_), *std::get<i>(current_)))andbool(invoke(*parent_->comp_, *std::get<i>(current_), *std::get<j>(current_)))arefalse. Then incrementsstd::get<i>(current_)and callssatisfy().-9- Returns:
*this.
constexpr void operator++(int);
-10- Effects: Equivalent to
++*this.
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires (equality_comparable<iterator_t<maybe-const<Const, Views>>> && ...);
-11- Effects: Equivalent to:
return x.current_ == y.current_;
friend constexpr bool operator==(const iterator& i, default_sentinel_t) noexcept;
-12- Effects: Equivalent to:
return i.active_idx_ == inactive;
friend constexpr concat-rvalue-reference-t<maybe-const<Const, Views>...> iter_move(const iterator& it)
noexcept(see below);
-13- Preconditions:
it.active_idx_ != inactiveistrue.-14- Effects: Let
ibeactive_idx_. Equivalent to:return ranges::iter_move(std::get<i>(it.current_));-15- Remarks: The exception specification is equivalent to:
((is_nothrow_invocable_v<decltype(ranges::iter_move), const iterator_t<maybe-const<Const, Views>>&> && is_nothrow_convertible_v<range_rvalue_reference_t<maybe-const<Const, Views>>, concat-rvalue-reference-t<maybe-const<Const, Views>...>>) && ...)
Add 25.7.? Set difference view [range.set.difference] as indicated:
[25.7.?.1] Overview [range.set.difference.overview]
-1- set_difference_view presents a view of set difference between two
sorted
ranges.
-2-
The name views::set_difference denotes a customization point object
[customization.point.object].
Given subexpressions E, F and G, the expression views::set_difference(E, F) and views::set_difference(E, F, G) is
expression-equivalent to set_difference_view(ranges::less{}, E, F) and set_difference_view(E, F, G), respectively.
-3- [Example 1:
vector v1{1, 2, 5, 5, 5, 9};
vector v2{ 2, 5, 7};
println("{}", views::set_difference(v1, v2)); // prints [1, 5, 5, 9]
— end example]
[25.7.?.2] Class template set_difference_view [range.set.difference.view]
namespace std::ranges {
template<class Comp, input_range V1, input_range V2>
requires view<V1> && view<V2> && is_object_v<Comp> &&
indirect_strict_weak_order<Comp, iterator_t<V1>, iterator_t<V2>>
class set_difference_view : public view_interface<set_difference_view<Comp, V1, V2>> {
movable-box<Comp> comp_; // exposition only
V1 base1_ = V1(); // exposition only
V2 base2_ = V2(); // exposition only
// [range.set.difference.iterator], class set_difference_view::iterator
class iterator; // exposition only
public:
set_difference_view() requires default_initializable<V1> && default_initializable<V2> &&
default_initializable<Comp> = default;
constexpr explicit set_difference_view(Comp comp, V1 base1, V2 base2);
constexpr V1 base() const & requires copy_constructible<V1> { return base1_; }
constexpr V1 base() && { return std::move(base1_); }
constexpr iterator begin();
constexpr default_sentinel_t end() const noexcept { return default_sentinel; }
constexpr auto reserve_hint() requires approximately_sized_range<V1>
{ return ranges::reserve_hint(base1_); }
constexpr auto reserve_hint() const requires approximately_sized_range<const V1>
{ return ranges::reserve_hint(base1_); }
};
template<class Comp, class R1, class R2>
set_difference_view(Comp, R1&&, R2&&)
-> set_difference_view<Comp, views::all_t<R1>, views::all_t<R2>>;
}
constexpr explicit set_difference_view(Comp comp, V1 base1, V2 base2);
-1- Effects: Initializes
comp_withstd::move(comp),base1_withstd::move(base1), andbase2_withstd::move(base2). The behavior is erroneous if eitherbase1_orbase2_is not sorted with respect tocomp.
constexpr iterator begin();
-2- Returns:
{this, ranges::begin(base1_), ranges::begin(base2_)}.-3- Remarks: In order to provide the amortized constant time complexity required by the
rangeconcept whenset_difference_viewmodelsforward_range, this function caches the result within theset_difference_viewfor use on subsequent calls.
[25.7.?.2] Class set_difference_view::iterator
[range.set.difference.iterator]
namespace std::ranges {
template<class Comp, input_range V1, input_range V2>
requires view<V1> && view<V2> && is_object_v<Comp> &&
indirect_strict_weak_order<Comp, iterator_t<V1>, iterator_t<V2>>
class set_difference_view<Comp, V1, V2>>::iterator {
iterator_t<V1> current1_ = iterator_t<V1>(); // exposition only
iterator_t<V2> current2_ = iterator_t<V2>(); // exposition only
set_difference_view* parent_ = nullptr; // exposition only
constexpr void satisfy(); // exposition only
constexpr iterator(set_difference_view* parent,
iterator_t<V1> current1, iterator_t<V2> current2); // exposition only
public:
using iterator_category = see below; // not always present
using iterator_concept =
conditional_t<forward_range<V1> && forward_range<V2>, forward_iterator_tag, input_iterator_tag>;
using value_type = range_value_t<V1>;
using difference_type = range_difference_t<V1>;
iterator()
requires default_initializable<iterator_t<V1>> && default_initializable<iterator_t<V2>>
= default;
constexpr iterator_t<V1> base() && { return std::move(current1_); }
constexpr const iterator_t<V1>& base() const & noexcept { return current1_; }
constexpr decltype(auto) operator*() const { return *current1_; }
constexpr iterator& operator++();
constexpr void operator++(int);
constexpr iterator operator++(int) requires forward_range<V1> && forward_range<V2> = default;
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires equality_comparable<iterator_t<V1>>;
friend constexpr bool operator==(const iterator& x, default_sentinel_t);
friend constexpr decltype(auto) iter_move(const iterator& i)
noexcept(noexcept(ranges::iter_move(i.current1_)));
};
}
-1- The member typedef-name
iterator_categoryis defined if and only ifV1andV2modelforward_range. In that case,iterator_categorydenotesforward_iterator_tagif qualified-iditerator_traits<iterator_t<V1>>::iterator_categoryanditerator_traits<iterator_t<V2>>::iterator_categorydenote a type that modelsderived_from<forward_iterator_tag>; otherwise it denotesinput_iterator_tag.
constexpr void satisfy();
-2- Effects: Equivalent to:
[Note 1:while (true) { if (current1_ == ranges::end(parent_->base1_)) return; if (current2_ == ranges::end(parent_->base2_)) return; if (invoke(*parent_->comp_, *current1_, *current2_)) return; if (invoke(*parent_->comp_, *current2_, *current1_)) ++current2_; else { ++current1_; ++current2_; } }set_difference_viewiterators use thesatisfyfunction to find the next valid element in the first range.— end note]
constexpr iterator(set_difference_view* parent, iterator_t<V1> current1, iterator_t<V2> current2);
-3- Effects: Initializes
parent_withparent,current1_withstd::move(current1)andcurrent2_withstd::move(current2); then callssatisfy().
constexpr iterator& operator++();
-4- Effects: Equivalent to:
++current1_; satisfy(); return *this;
constexpr void operator++(int);
-5- Effects: Equivalent to
++*this.
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires equality_comparable<iterator_t<V1>>;
-6- Effects: Equivalent to:
return x.current1_ == y.current1_;
friend constexpr bool operator==(const iterator& x, default_sentinel_t);
-7- Effects: Equivalent to:
return x.current1_ == x.end1_;
friend constexpr decltype(auto) iter_move(const iterator& i)
noexcept(noexcept(ranges::iter_move(i.current1_)));
-8- Effects: Equivalent to:
return ranges::iter_move(i.current1_);
Add 25.7.? Set symmetric difference view [range.set.symmetric.difference] as indicated:
[25.7.?.1] Overview [range.set.symmetric.difference.overview]
-1- set_symmetric_difference_view presents a view of set difference
between two
sorted
ranges.
-2-
The name views::set_symmetric_difference denotes a customization point object
[customization.point.object].
Given subexpressions E, F and G, the expression views::set_symmetric_difference(E, F) and views::set_symmetric_difference(E, F, G) is
expression-equivalent to set_symmetric_difference_view(ranges::less{}, E, F) and set_symmetric_difference_view(E, F, G), respectively.
-3- [Example 1:
vector v1{1, 3, 4, 6, 7, 9};
vector v2{1, 4, 5, 6, 9};
println("{}", views::set_symmetric_difference(v1, v2)); // prints [3 5 7]
— end example]
[25.7.?.2] Class template set_symmetric_difference_view
[range.set.symmetric.difference.view]
namespace std::ranges {
template<class Comp, input_range V1, input_range V2>
requires view<V1> && view<V2> && is_object_v<Comp> &&
indirect_strict_weak_order<Comp, iterator_t<V1>, iterator_t<V2>> &&
concatable<V1, V2>
class set_symmetric_difference_view : public view_interface<set_symmetric_difference_view<Comp, V1, V2>> {
movable-box<Comp> comp_; // exposition only
V1 base1_ = V1(); // exposition only
V2 base2_ = V2(); // exposition only
// [range.set.symmetric.difference.iterator], class set_symmetric_difference_view::iterator
class iterator; // exposition only
public:
set_symmetric_difference_view() requires default_initializable<V1> && default_initializable<V2> &&
default_initializable<Comp> = default;
constexpr explicit set_symmetric_difference_view(Comp comp, V1 base1, V2 base2);
constexpr iterator begin();
constexpr default_sentinel_t end() const noexcept { return default_sentinel; }
constexpr auto reserve_hint() requires approximately_sized_range<V1> &&
approximately_sized_range<V2>
{ return ranges::reserve_hint(base1_) + ranges::reserve_hint(base2_); }
constexpr auto reserve_hint() const requires approximately_sized_range<const V1> &&
approximately_sized_range<const V2>
{ return ranges::reserve_hint(base1_) + ranges::reserve_hint(base2_); }
};
template<class Comp, class R1, class R2>
set_symmetric_difference_view(Comp, R1&&, R2&&)
-> set_symmetric_difference_view<Comp, views::all_t<R1>, views::all_t<R2>>;
}
constexpr explicit set_symmetric_difference_view(Comp comp, V1 base1, V2 base2);
-1- Effects: Initializes
comp_withstd::move(comp),base1_withstd::move(base1), andbase2_withstd::move(base2). The behavior is erroneous if eitherbase1_orbase2_is not sorted with respect tocomp.
constexpr iterator begin();
-2- Returns:
{this, ranges::begin(base1_), ranges::begin(base2_)}.-3- Remarks: In order to provide the amortized constant time complexity required by the
rangeconcept whenset_symmetric_difference_viewmodelsforward_range, this function caches the result within theset_symmetric_difference_viewfor use on subsequent calls.
[25.7.?.2] Class set_symmetric_difference_view::iterator
[range.set.symmetric.difference.iterator]
namespace std::ranges {
template<class Comp, input_range V1, input_range V2>
requires view<V1> && view<V2> && is_object_v<Comp> &&
indirect_strict_weak_order<Comp, iterator_t<V1>, iterator_t<V2>>
class set_symmetric_difference_view<Comp, V1, V2>>::iterator {
static constexpr size_t inactive = -1; // exposition only
iterator_t<V1> current1_ = iterator_t<V1>(); // exposition only
iterator_t<V2> current2_ = iterator_t<V2>(); // exposition only
set_symmetric_difference_view* parent_ = nullptr; // exposition only
size_t active_idx_ = inactive; // exposition only
constexpr void satisfy(); // exposition only
constexpr iterator(set_symmetric_difference_view* parent,
iterator_t<V1> current1, iterator_t<V2> current2); // exposition only
public:
using iterator_category = see below; // not always present
using iterator_concept =
conditional_t<forward_range<V1> && forward_range<V2>, forward_iterator_tag, input_iterator_tag>;
using value_type = range_value_t<V1>;
using difference_type = range_difference_t<V1>;
iterator()
requires default_initializable<iterator_t<V1>> && default_initializable<iterator_t<V2>>
= default;
constexpr concat-reference-t<V1, V2> operator*() const;
constexpr iterator& operator++();
constexpr void operator++(int);
constexpr iterator operator++(int) requires forward_range<V1> && forward_range<V2> = default;
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires equality_comparable<iterator_t<V1>> && equality_comparable<iterator_t<V2>>;
friend constexpr bool operator==(const iterator& x, default_sentinel_t) noexcept;
friend constexpr concat-rvalue-reference-t<V1, V2> iter_move(const iterator& i) noexcept(see below);
};
}
-1- The member typedef-name
iterator_categoryis defined if and only ifV1andV2modelforward_range. In that case,iterator_categorydenotesforward_iterator_tagif qualified-iditerator_traits<iterator_t<V1>>::iterator_categoryanditerator_traits<iterator_t<V2>>::iterator_categorydenote a type that modelsderived_from<forward_iterator_tag>; otherwise it denotesinput_iterator_tag.
constexpr void satisfy();
-2- Effects: Equivalent to:
[Note 1:while (true) { if (current1_ == ranges::end(parent_->base1)) { if (current2_ == ranges::end(parent_->base2)) active_idx_ = inactive; else active_idx_ = 1; return; } if (current2_ == ranges::end(parent_->base2)) { active_idx_ = 0; return; } if (invoke(*parent_->comp_, *current1_, *current2_)) { active_idx_ = 0; return; } if (invoke(*parent_->comp_, *current2_, *current1_)) { active_idx_ = 1; return; } ++current1_; ++current2_; }set_symmetric_difference_viewiterators use thesatisfyfunction to find the next valid element in two ranges.— end note]
constexpr iterator(set_symmetric_difference_view* parent, iterator_t<V1> current1, iterator_t<V2> current2);
-3- Effects: Initializes
parent_withparent,current1_withstd::move(current1), andcurrent2_withstd::move(current2); then callssatisfy().
constexpr concat-reference-t<V1, V2> operator*() const;
-4- Preconditions:
active_idx_ != inactiveistrue.-5- Effects: Equivalent to:
if (active_idx_ == 0) return *current1_; return *current2_;
constexpr iterator& operator++();
-6- Preconditions:
active_idx_ != inactiveistrue.-7- Effects: Equivalent to:
if (active_idx_ == 0) ++current1_; else ++current2_; satisfy(); return *this;
constexpr void operator++(int);
-8- Effects: Equivalent to
++*this.
friend constexpr bool operator==(const iterator& x, const iterator& y)
requires equality_comparable<iterator_t<V1>> && equality_comparable<iterator_t<V2>>;
-9- Effects: Equivalent to:
return x.current1_ == y.current1_ && x.current2_ == y.current2_;
friend constexpr bool operator==(const iterator& i, default_sentinel_t) noexcept;
-10- Effects: Equivalent to:
return i.active_idx_ == inactive;
friend constexpr concat-rvalue-reference-t<V1, V2> iter_move(const iterator& i) noexcept(see below);
-11- Preconditions:
i.active_idx_ != inactiveistrue.-12- Effects: Equivalent to:
if (i.active_idx_ == 0) return ranges::iter_move(i.current1_); return ranges::iter_move(i.current2_);-13- Remarks: The exception specification is equivalent to:
noexcept(ranges::iter_move(i.current1_)) && noexcept(ranges::iter_move(i.current2_)) && is_nothrow_convertible_v<range_rvalue_reference_t<V1>, concat-rvalue-reference-t<V1, V2>> && is_nothrow_convertible_v<range_rvalue_reference_t<V2>, concat-rvalue-reference-t<V1, V2>>
views::set_operations implementation. URL: https://github.com/ericniebler/range-v3/blob/master/include/range/v3/view/set_algorithm.hpp