4050. Should views::iota(0) | views::take(5) be views::iota(0, 5)?

Section: 25.7.10.1 [range.take.overview], 25.7.10.1 [range.take.overview] Status: New Submitter: Hewill Kang Opened: 2024-01-28 Last modified: 2024-01-28

Priority: Not Prioritized

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

View all issues with New status.

Discussion:

Given that C++20 ranges does not introduce the infinite range notification present in range/v3, this means that views::iota(0) | views::take(5) will currently return a take_view object that does not model sized_range.

However, with the introduction of C++23 repeat_view, its interaction with views::take/drop does have special handling depending on whether it is an infinite range, which causes views::repeat(0) | views::take(5) to return a repeat_view objects that satisfy sized_range.

This inconsistency leads to very different behavior of these two range factories in the case of infinite ranges (demo):

#include <ranges>

auto take_and_drop = std::views::drop(5)
                   | std::views::take(4)
                   | std::views::drop(3)
                   | std::views::take(2)
                   | std::views::drop(1);

// The type of iota is drop_view<take_view<drop_view<take_view<drop_view<iota_view<int, unreachable_sentinel_t>>>>>>, which is indeed a template bloat.
auto iota = std::views::iota(0) | take_and_drop;
static_assert(std::ranges::sized_range<decltype(iota)>); // failed

// The type of repeat is simply std::ranges::repeat_view<int, long>
std::ranges::sized_range auto repeat = std::views::repeat(0) | take_and_drop; // ok

If we do account for the infinity of repeat_view, then I see no reason not to do it for iota_view, as this is obviously intuitive and can indeed be considered an enhancement.

Proposed resolution:

This wording is relative to N4971.

  1. Modify 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 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, then U is span<typename T::element_type>;

      2. (2.2.2) — otherwise, if T is a specialization of basic_string_view, then U is T;

      3. (2.2.3) — otherwise, T is a specialization of subrange, and U is subrange<iterator_t<T>>;

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

    4. (2.?) — Otherwise, if T is a specialization of iota_view that models random_access_range and same_as<sentinel_t<T>, unreachable_sentinel_t> is true, then views::iota(*ranges::begin(E), *(ranges::begin(E) + static_cast<D>(F))), except that E is evaluated only once.

    5. (2.4) — Otherwise, if T is a specialization of repeat_view (25.6.5.2 [range.repeat.view]):

      1. (2.4.1) — if T models sized_range, then

          views::repeat(*E.value_, std::min<D>(ranges::distance(E), F))
        except that E is evaluated only once;

      2. (2.4.2) — otherwise, views::repeat(*E.value_, static_cast<D>(F)).

    6. (2.5) — Otherwise, take_view(E, F).

  2. Modify 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 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]),

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

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

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

      then U(ranges::begin(E) + std::min<D>(ranges::distance(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 iota_view that models random_access_range and same_as<sentinel_t<T>, unreachable_sentinel_t> is true, then views::iota(*(ranges::begin(E) + static_cast<D>(F))).

    4. (2.3) — Otherwise, if T is a specialization of 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.

    5. (2.4) — Otherwise, if T is a specialization of repeat_view (25.6.5.2 [range.repeat.view]):

      1. (2.4.1) — if T models sized_range, then

          views::repeat(*E.value_, ranges::distance(E) - std::min<D>(ranges::distance(E), F))
        except that E is evaluated only once;

      2. (2.4.2) — otherwise, ((void)F, decay-copy(E)), except that the evaluations of E and F are indeterminately sequenced.

    6. (2.5) — Otherwise, drop_view(E, F).