Title
Generic code cannot call ranges::advance(i, s)
Status
new
Section
[range.iter.op.advance][iterator.concept.sentinel]
Submitter
Casey Carter

Created on 2020-06-18.00:00:00 last changed 1 week ago

Messages

Date: 2020-06-26.11:48:00

Proposed resolution:

This wording is relative to N4861.

Wording for both Option A and Option B are provided.

Option A:

  1. Modify [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 [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 [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) — […]

Date: 2020-06-15.00:00:00

[ 2020-06-26; Reflector prioritization ]

Set priority to 2 after reflector discussions.

Date: 2020-06-18.00:00:00

The specification of the iterator & sentinel overload of ranges::advance in [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.

History
Date User Action Args
2020-06-26 11:48:00adminsetmessages: + msg11345
2020-06-20 21:59:14adminsetmessages: + msg11339
2020-06-18 00:00:00admincreate