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_conceptdenotesforward_iterator_tagifImodelsforward_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?