This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4510. Ambiguity of std::ranges::advance and std::ranges::next when the difference type is also a sentinel type

Section: 24.4.4.2 [range.iter.op.advance], 24.4.4.4 [range.iter.op.next] Status: New Submitter: Jiang An Opened: 2026-01-09 Last modified: 2026-01-18

Priority: Not Prioritized

View all other issues in [range.iter.op.advance].

View all issues with New status.

Discussion:

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.

Proposed resolution:

This wording is relative to N5032.

  1. Modify 24.2 [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
    
      […]
      // 24.4.4 [range.iter.ops], range iterator operations
      namespace ranges {
        // 24.4.4.2 [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);    
       […]
        // 24.4.4.4 [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 24.4.4.2 [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 24.4.4.4 [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: […]