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

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

Messages

Date: 2025-12-20.11:22:52

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.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
2025-12-20 11:22:52adminsetmessages: + msg15828
2025-12-12 00:00:00admincreate