3407. Some problems with the wording changes of P1739R4

Section: 25.7.10.1 [range.take.overview], 25.6.4 [range.iota] Status: C++23 Submitter: Patrick Palka Opened: 2020-02-21 Last modified: 2023-11-22

Priority: 2

View all other issues in [range.take.overview].

View all issues with C++23 status.

Discussion:

Section 6.1 of P1739R4 changes the specification of views::take as follows:

-2- The name views::take denotes a range adaptor object (25.7.2 [range.adaptor.object]). Given subexpressions E and F, the expression views::take(E, F) is expression-equivalent to take_view{E, F}. Let E and F be expressions, let T be remove_cvref_t<decltype((E))>, and let D be range_difference_t<decltype((E))>. If decltype((F)) does not model convertible_to<D>, views::take(E, F) is ill-formed. Otherwise, the expression views::take(E, F) is expression-equivalent to:

  1. — if T is a specialization of ranges::empty_view (25.6.2.2 [range.empty.view]), then ((void) F, decay-copy(E));

  2. — otherwise, if T models random_access_range and sized_range and is

    1. — a specialization of span (23.7.2.2 [views.span]) where T::extent == dynamic_extent,

    2. — a specialization of basic_string_view (27.3 [string.view]),

    3. — a specialization of ranges::iota_view (25.6.4.2 [range.iota.view]), or

    4. — a specialization of ranges::subrange (25.5.4 [range.subrange]),

    then T{ranges::begin(E), ranges::begin(E) + min<D>(ranges::size(E), F)}, except that E is evaluated only once;

  3. — otherwise, ranges::take_view{E, F}.

Consider the case when T = subrange<counted_iterator<int>, default_sentinel_t>. Then according to the above wording, views::take(E, F) is expression-equivalent to

T{ranges::begin(E), ranges:begin(E) + min<D>(ranges::size(E), F)};   (*)

But this expression is ill-formed for the T we chose because subrange<counted_iterator<int>, default_sentinel_t> has no matching constructor that takes an iterator-iterator pair.

More generally the above issue applies anytime T is a specialization of subrange that does not model common_range. But a similar issue also exists when T is a specialization of iota_view whose value type differs from its bound type. In this case yet another issue arises: In order for the expression (*) to be well-formed when T is a specialization of iota_view, we need to be able to construct an iota_view out of an iterator-iterator pair, and for that it seems we need to add another constructor to iota_view.

[2020-02-24, Casey comments]

Furthermore, the pertinent subrange constructor is only available when subrange::StoreSize is false (i.e., when either the subrange specialization's third template argument is not subrange_kind::sized or its iterator and sentinel types I and S model sized_sentinel_for<S, I>).

[2020-03-16, Tomasz comments]

A similar problem occurs for the views::drop for the subrange<I, S, subrange_kind::sized>, that explicitly stores size (i.e. sized_sentinel_for<I, S> is false). In such case, the (iterator, sentinel) constructor that views::drop will be expression-equivalent is not available.

[2020-03-29 Issue Prioritization]

Priority to 2 after reflector discussion.

[2021-05-18 Tim adds wording]

The proposed resolution below is based on the MSVC implementation, with one caveat: the MSVC implementation uses a SCARY iterator type for iota_view and therefore its iota_view case for take is able to directly construct the new view from the iterator type of the original. This is not required to work, so the wording below constructs the iota_view from the result of dereferencing the iterators instead in this case.

[2021-06-18 Tim syncs wording to the current working draft]

The wording below also corrects the size calculation in the presence of integer-class types.

[2021-08-20; LWG telecon]

Set status to Tentatively Ready after telecon review.

[2021-10-14 Approved at October 2021 virtual plenary. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4892.

  1. Edit 25.7.10.1 [range.take.overview] as indicated:

    -2- The name views::take denotes a range adaptor object (25.7.2 [range.adaptor.object]). Let E and F be expressions, let T be remove_cvref_t<decltype((E))>, and let D be range_difference_t<decltype((E))>. If decltype((F)) does not model convertible_to<D>, views::take(E, F) is ill-formed. Otherwise, the expression views::take(E, F) is expression-equivalent to:

    1. (2.1) — if T is a specialization of ranges::empty_view (25.6.2.2 [range.empty.view]), then ((void) F, decay-copy(E)), except that the evaluations of E and F are indeterminately sequenced;

    2. (2.2) — otherwise, if T models random_access_range and sized_range, and is a specialization of span (23.7.2.2 [views.span]), basic_string_view (27.3 [string.view]), or ranges::subrange (25.5.4 [range.subrange]), then U(ranges::begin(E), ranges::begin(E) + std::min<D>(ranges::distance(E), F)), except that E is evaluated only once, where U is a type determined as follows:

      1. (2.2.1) — if T is a specialization of span (23.7.2.2 [views.span]) where T::extent == dynamic_extent, then U is span<typename T::element_type>;

      2. (2.2.2) — otherwise, if T is a specialization of basic_string_view (27.3 [string.view]), then U is T;

      3. (2.2.3) — a specialization of ranges::iota_view (25.6.4.2 [range.iota.view]), or

      4. (2.2.4) — otherwise, T is a specialization of ranges::subrange (25.5.4 [range.subrange]), and U is ranges::subrange<iterator_t<T>>;

      then T{ranges::begin(E), ranges::begin(E) + min<D>(ranges::size(E), F)}, except that E is evaluated only once;

    3. (2.?) — otherwise, if T is a specialization of ranges::iota_view (25.6.4.2 [range.iota.view]) that models random_access_range and sized_range, then ranges::iota_view(*ranges::begin(E), *(ranges::begin(E) + std::min<D>(ranges::distance(E), F))), except that E and F are each evaluated only once;

    4. (2.3) — otherwise, ranges::take_view(E, F).

  2. Edit 25.7.12.1 [range.drop.overview] as indicated:

    -2- The name views::drop denotes a range adaptor object (25.7.2 [range.adaptor.object]). Let E and F be expressions, let T be remove_cvref_t<decltype((E))>, and let D be range_difference_t<decltype((E))>. If decltype((F)) does not model convertible_to<D>, views::drop(E, F) is ill-formed. Otherwise, the expression views::drop(E, F) is expression-equivalent to:

    1. (2.1) — if T is a specialization of ranges::empty_view (25.6.2.2 [range.empty.view]), then ((void) F, decay-copy(E)), except that the evaluations of E and F are indeterminately sequenced;

    2. (2.2) — otherwise, if T models random_access_range and sized_range, and is

      1. (2.2.1) —a specialization of span (23.7.2.2 [views.span]) where T::extent == dynamic_extent,

      2. (2.2.2) — a specialization of basic_string_view (27.3 [string.view]),

      3. (2.2.3) — a specialization of ranges::iota_view (25.6.4.2 [range.iota.view]), or

      4. (2.2.4) — a specialization of ranges::subrange (25.5.4 [range.subrange]) where T::StoreSize is false,

      then TU(ranges::begin(E) + std::min<D>(ranges::sizedistance(E), F), ranges::end(E)), except that E is evaluated only once, where U is span<typename T::element_type> if T is a specialization of span and T otherwise;

    3. (2.?) — otherwise, if T is a specialization of ranges::subrange (25.5.4 [range.subrange]) that models random_access_range and sized_range, then T(ranges::begin(E) + std::min<D>(ranges::distance(E), F), ranges::end(E), to-unsigned-like(ranges::distance(E) - std::min<D>(ranges::distance(E), F))), except that E and F are each evaluated only once;

    4. (2.3) — otherwise, ranges::drop_view(E, F).