Title
Ambiguity of `std::ranges::advance` and `std::ranges::next` when the difference type is also a sentinel type
Status
new
Section
[range.iter.op.advance][range.iter.op.next]
Submitter
Jiang An

Created on 2026-01-09.00:00:00 last changed 3 days ago

Messages

Date: 2026-01-18.12:36:04

Proposed resolution:

This wording is relative to N5032.

  1. Modify [iterator.synopsis], header <iterator> synopsis, as indicated:

    […]
    namespace std {
      template<class T> using with-reference = T&amp;; // exposition only
      template<class T> concept can-reference      // exposition only
        = requires { typename with-reference<T>; };
      template<class T> concept dereferenceable    // exposition only
        = requires(T&amp; 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    
      }
      […]
    }
    
  2. 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: […]

  3. 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: […]

Date: 2026-01-09.00:00:00

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:04adminsetmessages: + msg15889
2026-01-09 00:00:00admincreate