Created on 2026-01-09.00:00:00 last changed 3 days ago
Proposed resolution:
This wording is relative to N5032.
Modify [iterator.synopsis], header <iterator> synopsis, as indicated:
[…]
namespace std {
template<class T> using with-reference = T&; // exposition only
template<class T> concept can-reference // exposition only
= requires { typename with-reference<T>; };
template<class T> concept dereferenceable // exposition only
= requires(T& t) {
{ *t } -> can-reference; // not required to be equality-preserving
};
template<class T>
constexpr bool is-integer-like = see below; // exposition only
[…]
// [range.iter.ops], range iterator operations
namespace ranges {
// [range.iter.op.advance], ranges::advance
template<input_or_output_iterator I>
constexpr void advance(I& i, iter_difference_t<I> n); // freestanding
template<input_or_output_iterator I, sentinel_for<I>class S>
requires (!is-integer-like<S>) && sentinel_for<S, I>
constexpr void advance(I& i, S bound); // freestanding
template<input_or_output_iterator I, sentinel_for<I> S>
constexpr iter_difference_t<I> advance(I& i, iter_difference_t<I> n, // freestanding
S bound);
[…]
// [range.iter.op.next], ranges::next
template<input_or_output_iterator I>
constexpr I next(I x); // freestanding
template<input_or_output_iterator I>
constexpr I next(I x, iter_difference_t<I> n); // freestanding
template<input_or_output_iterator I, sentinel_for<I>class S>
requires (!is-integer-like<S>) && sentinel_for<S, I>
constexpr I next(I x, S bound); // freestanding
template<input_or_output_iterator I, sentinel_for<I> S>
constexpr I next(I x, iter_difference_t<I> n, S bound); // freestanding
}
[…]
}
Modify [range.iter.op.advance] as indicated:
template<input_or_output_iterator I,sentinel_for<I>class S> requires (!is-integer-like<S>) && sentinel_for<S, I> constexpr void advance(I& i, S bound);-3- Preconditions: […]
Modify [range.iter.op.next] as indicated:
template<input_or_output_iterator I,sentinel_for<I>class S> requires (!is-integer-like<S>) && sentinel_for<S, I> constexpr I next(I x, S bound);-3- Effects: […]
Currently, `ranges::advance` and `ranges::next` have `operator()` overloads that accept `(iterator, sentinel)` and `(iterator, difference)`. However, when the difference type of the iterator is also a sentinel type, there may be ambiguity in both overloads.
E.g. the following example is rejected when compiling with libc++ or libstdc++ (demo):
#include <cstddef>
#include <iterator>
#include <type_traits>
template<class T>
struct FwdIter { // triggers ADL for T
FwdIter();
using value_type = std::remove_cv_t<T>;
using difference_type = int;
T& operator*() const;
FwdIter& operator++();
FwdIter operator++(int);
friend bool operator==(const FwdIter&, const FwdIter&);
};
static_assert(std::forward_iterator<FwdIter<int>>);
struct OmniConv {
OmniConv(const auto&);
friend bool operator==(OmniConv, OmniConv); // found by ADL via things related to OmniConv
};
int main() {
FwdIter<OmniConv> it{};
std::ranges::advance(it, 0); // ambiguous
std::ranges::next(it, 0); // ambiguous
}
Perhaps it would be better to ensure that calling `ranges::advance` or `ranges::next` with an iterator value and a value of the difference type is unambiguous. Note that wrapping iterators that trigger ADL for the value type like the `FwdIter` in this example are common in standard library implementations, so ambiguity can be easily raised from containers with such `OmniConv` being their element type.
| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2026-01-18 12:36:04 | admin | set | messages: + msg15889 |
| 2026-01-09 00:00:00 | admin | create | |