Created on 2025-12-12.00:00:00 last changed 1 week ago
Proposed resolution:
This wording is relative to N5032.
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)`.
[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:
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.
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:52 | admin | set | messages: + msg15828 |
| 2025-12-12 00:00:00 | admin | create | |