Issue 3392: ranges::distance() cannot be used on a move-only iterator with a sized sentinel (original) (raw)


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

3392. ranges::distance() cannot be used on a move-only iterator with a sized sentinel

Section: 24.4.4.3 [range.iter.op.distance] Status: C++23 Submitter: Patrick Palka Opened: 2020-02-07 Last modified: 2023-11-22

Priority: 3

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

View all issues with C++23 status.

Discussion:

One cannot use ranges::distance(I, S) to compute the distance between a move-only counted_iterator and the default_sentinel. In other words, the following is invalid

// iter is a counted_iterator with an move-only underlying iterator ranges::distance(iter, default_sentinel);

and yet

(default_sentinel - iter);

is valid. The first example is invalid because ranges::distance() takes its first argument by value so when invoking it with an iterator lvalue argument the iterator must be copyable, which a move-only iterator is not. The second example is valid because counted_iterator::operator-()takes its iterator argument by const reference, so it doesn't require copyability of the counted_iterator.

This incongruency poses an inconvenience in generic code which usesranges::distance() to efficiently compute the distance between two iterators or between an iterator-sentinel pair. Although it's a bit of an edge case, it would be good if ranges::distance() does the right thing when the iterator is a move-only lvalue with a sized sentinel.

If this is worth fixing, one solution might be to define a separate overload of ranges::distance(I, S) that takes its arguments by constreference, as follows.

[2020-02 Prioritized as P3 and LEWG Monday morning in Prague]

[2020-05-28; LEWG issue reviewing]

LEWG issue processing voted to accept the direction of 3392. Status change to Open.

Accept the direction of LWG3392

SF F N A SA 14 6 0 0 0

Previous resolution [SUPERSEDED]:

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

    #include

    namespace std {
    […]
    // 24.4.4 [range.iter.ops], range iterator operations
    namespace ranges {
    […]
    // 24.4.4.3 [range.iter.op.distance], ranges::distance
    template<input_or_output_iterator I, sentinel_for S>
    requires (!sized_sentinel_for<S, I>)

   constexpr iter_difference_t<I> distance(I first, S last);  

template<input_or_output_iterator I, sized_sentinel_for S>
constexpr iter_difference_t distance(const I& first, const S& last);
template
constexpr range_difference_t distance(R&& r);
[…]
}
[…]
} 2. Modify 24.4.4.3 [range.iter.op.distance] as indicated:
template<input_or_output_iterator I, sentinel_for S>
requires (!sized_sentinel_for<S, I>)
constexpr iter_difference_t ranges::distance(I first, S last);

-1- Preconditions: [first, last) denotes a range, or [last, first) denotes a range and S and I model same_as<S, I> && sized_sentinel_for<S, I>.

-2- Effects: If S and I model sized_sentinel_for<S, I>, returns(last - first); otherwise, rReturns the number of increments needed to get fromfirst to last.

template<input_or_output_iterator I, sized_sentinel_for S>
constexpr iter_difference_t ranges::distance(const I& first, const S& last);

-?- Preconditions: S and I model sized_sentinel_for<S, I> and either:

  1. (?.1) — [first, last) denotes a range, or
  2. (?.2) — [last, first) denotes a range and S and I modelsame_as<S, I>.

-? Effects: Returns (last - first);

[2021-05-19 Tim updates wording]

The wording below removes the explicit precondition on the sized_sentinel_foroverload of distance, relying instead on the semantic requirements of that concept and the "Effects: Equivalent to:" word of power. This also removes the potentially surprising inconsistency that given a non-emptystd::vector<int> v,ranges::distance(v.begin(), v.cend()) is well-defined butranges::distance(v.cend(), v.begin()) is currently undefined.

[2021-06-23; Reflector poll]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

[2021-10-14 Approved at October 2021 virtual plenary. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4885.

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

    […]

    namespace std {
    […]
    // 24.4.4 [range.iter.ops], range iterator operations
    namespace ranges {
    […]
    // 24.4.4.3 [range.iter.op.distance], ranges::distance
    template<input_or_output_iterator I, sentinel_for S>
    requires (!sized_sentinel_for<S, I>)

   constexpr iter_difference_t<I> distance(I first, S last);  

template<input_or_output_iterator I, sized_sentinel_for S>
constexpr iter_difference_t distance(const I& first, const S& last);
template
constexpr range_difference_t distance(R&& r);
[…]
}
[…]
} 2. Modify 24.4.4.3 [range.iter.op.distance] as indicated:
template<input_or_output_iterator I, sentinel_for S>
requires (!sized_sentinel_for<S, I>)
constexpr iter_difference_t ranges::distance(I first, S last);

-1- Preconditions: [first, last) denotes a range, or [last, first) denotes a range and S and I model same_as<S, I> && sized_sentinel_for<S, I>.

-2- Effects: If S and I model sized_sentinel_for<S, I>, returns(last - first); otherwise, returns the Returns: The number of increments needed to get fromfirst to last.

template<input_or_output_iterator I, sized_sentinel_for S>
constexpr iter_difference_t ranges::distance(const I& first, const S& last);

-?- Effects: Equivalent to return last - first;