This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Tentatively NAD status.

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: Tentatively NAD Submitter: Hewill Kang Opened: 2024-01-28 Last modified: 2025-10-20

Priority: Not Prioritized

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

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

View all issues with Tentatively NAD 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.

[2025-10-20; Reflector poll; Status changed: New → Tentatively NAD.]

"This changes meaning of existing C++20 for unclear benefit. This would need a paper."

"Why does iota(0, 10) | take(5) give you iota(0, 5) but iota(0) | take(5) doesn't?"

"IIRC there was opposition to P1739 introducing any kind of special cases in the adaptor objects. What got consensus was only the 'specialisations' that preserve the exact type of the underlying range. Thus iota(0, 10)iota(0, 5) was fine, but iota(0)iota(0, 5) would not have been. I still think that all changes that simplify the return types are helpful, but it would certainly be a breaking change now."

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).