C++20 introduced a std::common_iterator
that is capable of representing a non-common range of elements (where the types of the iterator and sentinel differ) as a common range (where they are the same), its synopsis defines as:
template<input_or_output_iterator I, sentinel_for<I> S>
requires (!same_as<I, S> && copyable<I>)
class common_iterator {
// ...
private:
variant<I, S> v_; // exposition only
};
And it is useful for interfacing with legacy code that expects the begin and end of a range to have the same type.
In [iterators.common#common.iter.types-1.1], its iterator_concept
is defined as:
iterator_concept
denotesforward_iterator_tag
ifI
modelsforward_iterator
; otherwise it denotesinput_iterator_tag
.
Why common_iterator
can only be a forward_iterator
at most, and cannot completely define its iterator_concept
based on I
‘s iterator_category
? For example, if I
is random_asscess_iterator
, then common_iterator<I, S>
is random_asscess_iterator
, and so on.
It seems that this is technically feasible since common_iterator
just uses std::variant
to type erase I
and S
.
Consider following (godbolt):
auto r = views::iota(0) | std::views::take(5);
static_assert( ranges::random_access_range<decltype(r)>);
auto cr = r | views::common;
static_assert(!ranges::random_access_range<decltype(cr)>);
static_assert( ranges::forward_range<decltype(cr)>);
r
is random_access_range
, so C++20 constrained algorithms such as ranges::binary_search
can use this trait to perform more efficient operations on it, but in order to enable the old std
algorithm to apply it, we need to use views::common
to convert it to a common_range
. However, it also degenerates to a forward_range
, which reduces the efficiency of the algorithm.
Why does the standard only define common_iterator
as forward_iterator
at most? What are the considerations behind this?