drop_view
's const begin
should additionally require sized_range
Section: 25.7.12.2 [range.drop.view] Status: C++23 Submitter: Casey Carter Opened: 2020-08-31 Last modified: 2023-11-22
Priority: 0
View other active issues in [range.drop.view].
View all other issues in [range.drop.view].
View all issues with C++23 status.
Discussion:
When the underlying range models both random_access_range
and sized_range
, a
drop_view
can easily calculate its first iterator in 𝒪
(1) as by the
underlying range's first iterator plus the minimum of the number of elements to drop and the
size of the underlying range. In this case drop_view::begin
need not "cache the result
within the drop_view
for use on subsequent calls" "in order to provide the amortized
constant-time complexity required by the range
concept" (25.7.12.2 [range.drop.view]/4).
However, drop_view::begin() const
does not require sized_range
, it requires
only random_access_range
. There's no way to implementing what amounts to a requirement
that calls to begin
after the first must be 𝒪
(1) without memoization.
const
member function in a manner consistent with
16.4.6.10 [res.on.data.races] is impossible without some kind of thread synchronization. It
is not the intended design for anything in current Range library to require such implementation
heroics, we typically fall back to mutable-only iteration to avoid thread synchronization concerns.
(Note that both range-v3
and cmcstl2
handle drop_view::begin() const
incorrectly by performing 𝒪
(N
) lookup of the first iterator on each call to
begin
, which is consistent with 16.4.6.10 [res.on.data.races] but fails to meet the
complexity requirements imposed by the range
concept.) We should fall back to mutable-only
iteration here as well when the underlying range is not a sized_range
.
For drop_view
, changing the constraints on the const
overload of begin
also requires changing the constraints on the non-const
overload. The non-const begin
tries to constrain itself out of overload resolution when the const
overload would be valid
if the underlying range models the exposition-only simple-view
concept. (Recall that
T
models simple-view iff T
models view
, const T
models range
,
and T
and const T
have the same iterator and sentinel types.) Effectively this means
the constraints on the non-const
overload must require either that the underlying range fails
to model simple-view
or that the constraints on the const
overload would not
be satisfied. So when we add a new sized_range
requirement to the const
overload,
we must also add its negation to the mutable overload. (The current form of the constraint on the
mutable begin
overload is !(simple-view<V> && random_access_range<V>)
instead of !(simple-view<V> && random_access_range<const V>)
because
of an unstated premise that V
and const V
should both have the same category when
both are ranges. Avoiding this unstated premise would make it easier for future readers to grasp what's
happening here; we should formulate our new constraints in terms of const V
instead of V
.)
[2020-09-29; Reflector discussions]
Status to Tentatively Ready and priority to 0 after five positive votes on the reflector.
[2020-11-09 Approved In November virtual meeting. Status changed: Tentatively Ready → WP.]
Proposed resolution:
This wording is relative to N4861.
Modify 25.7.12.2 [range.drop.view] as indicated:
[…]namespace std::ranges { template<view V> class drop_view : public view_interface<drop_view<V>> { public: […] constexpr auto begin() requires (!(simple-view<V> && random_access_range<const V> && sized_range<const V>)); constexpr auto begin() const requires random_access_range<const V> && sized_range<const V>; […] }; }constexpr auto begin() requires (!(simple-view<V> && random_access_range<const V> && sized_range<const V>)); constexpr auto begin() const requires random_access_range<const V> && sized_range<const V>;-3- Returns:
-4- Remarks: In order to provide the amortized constant-time complexity required by theranges::next(ranges::begin(base_), count_, ranges::end(base_))
.range
concept whendrop_view
modelsforward_range
, the first overload caches the result within thedrop_view
for use on subsequent calls. [Note: Without this, applying areverse_view
over adrop_view
would have quadratic iteration complexity. — end note]