3453. Generic code cannot call ranges::advance(i, s)

Section: 24.4.4.2 [range.iter.op.advance], 24.3.4.7 [iterator.concept.sentinel] Status: C++23 Submitter: Casey Carter Opened: 2020-06-18 Last modified: 2023-11-22

Priority: 2

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

View all issues with C++23 status.

Discussion:

The specification of the iterator & sentinel overload of ranges::advance in 24.4.4.2 [range.iter.op.advance] reads:

template<input_or_output_iterator I, sentinel_for<I> S>
  constexpr void ranges::advance(I& i, S bound);

-3- Preconditions: [i, bound) denotes a range.

-4- Effects:

  1. (4.1) — If I and S model assignable_from<I&, S>, equivalent to i = std::move(bound).

  2. (4.2) — […]

The assignment optimization in bullet 4.1 is just fine for callers with concrete types who can decide whether or not to call advance depending on the semantics of the assignment performed. However, since this assignment operation isn't part of the input_or_output_iterator or sentinel_for requirements its semantics are unknown for arbitrary types. Effectively, generic code is forbidden to call this overload of advance when assignable_from<I&, S> is satisfied and non-generic code must tread lightly. This seems to make the library dangerously unusable. We can correct this problem by either:

  1. Making the assignment operation in question an optional part of the sentinel_for concept with well-defined semantics. This concept change should be relatively safe given that assignable_from<I&, S> requires common_reference_with<const I&, const S&>, which is very rarely satisfied inadvertently.

  2. Requiring instead same_as<I, S> to trigger the assignment optimization in bullet 4.1 above. S is semiregular, so i = std::move(s) is certainly well-formed (and has well-defined semantics thanks to semiregular) when I and S are the same type. The optimization will not apply in as many cases, but we don't need to make a scary concept change, either.

[2020-06-26; Reflector prioritization]

Set priority to 2 after reflector discussions.

[2020-08-21; Issue processing telecon: Option A is Tentatively Ready]

Previous resolution [SUPERSEDED]:

This wording is relative to N4861.

Wording for both Option A and Option B are provided.

Option A:

  1. Modify 24.3.4.7 [iterator.concept.sentinel] as indicated:

    template<class S, class I>
      concept sentinel_for =
        semiregular<S> &&
        input_or_output_iterator<I> &&
        weakly-equality-comparable-with<S, I>; // See 18.5.4 [concept.equalitycomparable]
    

    -2- Let s and i be values of type S and I such that [i, s) denotes a range. Types S and I model sentinel_for<S, I> only if

    1. (2.1) — i == s is well-defined.

    2. (2.2) — If bool(i != s) then i is dereferenceable and [++i, s) denotes a range.

    3. (2.?) — assignable_from<I&, S> is either modeled or not satisfied.

Option B:

  1. Modify 24.4.4.2 [range.iter.op.advance] as indicated:

    template<input_or_output_iterator I, sentinel_for<I> S>
      constexpr void ranges::advance(I& i, S bound);
    

    -3- Preconditions: [i, bound) denotes a range.

    -4- Effects:

    1. (4.1) — If I and S model assignable_from<I&, S>same_as<I, S>, equivalent to i = std::move(bound).

    2. (4.2) — […]

[2020-11-09 Approved In November virtual meeting. Status changed: Tentatively Ready → WP.]

Proposed resolution:

This wording is relative to N4861.

  1. Modify 24.3.4.7 [iterator.concept.sentinel] as indicated:

    template<class S, class I>
      concept sentinel_for =
        semiregular<S> &&
        input_or_output_iterator<I> &&
        weakly-equality-comparable-with<S, I>; // See 18.5.4 [concept.equalitycomparable]
    

    -2- Let s and i be values of type S and I such that [i, s) denotes a range. Types S and I model sentinel_for<S, I> only if

    1. (2.1) — i == s is well-defined.

    2. (2.2) — If bool(i != s) then i is dereferenceable and [++i, s) denotes a range.

    3. (2.?) — assignable_from<I&, S> is either modeled or not satisfied.