Title
Allow calling `std::ranges::size` in ranges algorithms
Status
open
Section
[algorithms.requirements]
Submitter
Ruslan Arutyunyan

Created on 2025-12-12.00:00:00 last changed 1 week ago

Messages

Date: 2026-01-30.18:02:58

Proposed resolution:

This wording is relative to N5032.

  1. Modify [algorithms.requirements] as indicated:

    […]

    -14- Overloads of algorithms that take `range` arguments ([range.range]) behave as if they are implemented by dispatching to the overload in namespace `ranges` that takes separate iterator and sentinel arguments, where for each range argument `r`

    • (14.1) — a corresponding iterator argument is initialized with `ranges::begin(r)` and

    • (14.2) — a corresponding sentinel argument is initialized with `ranges::end(r)`, or `ranges::next(ranges::begin(r), ranges::end(r))` if the type of `r` models `forward_range` and computing `ranges::next` meets the specified complexity requirements. `ranges::begin(r) + N` where `N` is equal to `ranges::distance(r)`.

Date: 2026-01-15.00:00:00

[ 2026-01-30; LWG telecon. Status → Open. ]

Date: 2026-01-15.00:00:00

[ 2026-01-30; Jonathan provides new wording ]

We can just say that the value is either the end iterator, or begin+N, without saying when or how the implementation obtains begin+N. It might use `ranges::next(ranges::begin(r), std::ranges::end(r))` or `ranges::next(ranges::begin(r), ranges::distance(r))` or something else, only the value matters not how it's obtained. Paragraph 11 in the same subclause defines what begin+N means if the iterator is not a random access iterator.

Date: 2026-01-15.00:00:00

[ 2026-01-16; Reflector poll. ]

Set priority to 3 after reflector poll.

"Should be 'the type of `r` models `sized_range`"

"It's a lot of words, could be shorter. Would prefer not to establish priority, but let implementation decide if/when to use `ranges::next`."

"Would `counted_iterator(ranges::begin(r), ranges::distance(r))` and `default_sentinel` be better?"

This wording is relative to N5032.

  1. Modify [algorithms.requirements] as indicated:

    […]

    -14- Overloads of algorithms that take `range` arguments ([range.range]) behave as if they are implemented by dispatching to the overload in namespace `ranges` that takes separate iterator and sentinel arguments, where for each range argument `r`

    • (14.1) — a corresponding iterator argument is initialized with `ranges::begin(r)` and

    • (14.2) — a corresponding sentinel argument is initialized with `ranges::end(r)`, or `ranges::next(ranges::begin(r), ranges::end(r))` if the type of `r` models `forward_range` and computing `ranges::next` meets the specified complexity requirements.one of the following:

      • (14.2.1) — if the type of `r` models `forward_range` and computing `ranges::next` meets the specified complexity requirements then

        • (14.2.1.1) — `ranges::next(ranges::begin(r), ranges::size(r))` if `r` models `sized_range`, otherwise

        • (14.2.1.2) — `ranges::next(ranges::begin(r), ranges::end(r))`.

      • (14.2.2) — Otherwise, `std::ranges::end(s)`.

Date: 2025-12-12.00:00:00

[algorithms.requirements] paragraph 14 says that the algorithms in `ranges` namespace are implemented as if they are dispatched to the corresponding overload with iterator and sentinel. However, there are two problems with the current wording:

  1. We only allow to call either `std::ranges::end(r)` or `std::ranges::next(std::ranges::begin(r)`, `std::ranges::end())` to calculate a corresponding sentinel. However, this is a pessimization for some ranges because we can have sized ranges without sized_sentinel_for. Consider the following example:

    const char str[] = "something";
    ranges::subrange<const char*,
                    null_sentinel_t,
                    subrange_kind::sized> sr(ranges::begin(str),
                                              null_sentinel,
                                              ranges::size(str) - 1);
    my::ranges::next(sr.begin(), sr.end()); // this line serially calculates iterator that is equal to sr.end()
    

    Despite the fact that we know the size of the range and that the range is the random access one, `std::ranges::next` calculates the iterator serially, because it only has constant time complexity optimization for `sized_sentinel_for`. We could clearly achieve better complexity with a different API or with the different overload of the same API.

  2. It is unclear when an algorithm calls `std::ranges::end(r)` and when it calls `std::ranges::next(std::ranges::begin(r), std::ranges::end())` to calculate the corresponding sentinel because there is no established priority between them. Maybe, it's smaller problem but still it's worth clarifying in my opinion.

History
Date User Action Args
2026-01-30 23:38:56adminsetstatus: ready -> open
2026-01-30 18:22:04adminsetstatus: new -> ready
2026-01-30 18:02:58adminsetmessages: + msg15909
2026-01-30 18:02:58adminsetmessages: + msg15908
2026-01-16 22:15:24adminsetmessages: + msg15876
2025-12-20 11:22:52adminsetmessages: + msg15828
2025-12-12 00:00:00admincreate