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

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

Section: 23.4.4.3 [range.iter.op.distance] Status: WP Submitter: Patrick Palka Opened: 2020-02-07 Last modified: 2021-10-14

Priority: 3

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

View all issues with WP 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 uses `ranges::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 `const` reference, 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 23.2 [iterator.synopsis], header `<iterator>` synopsis, as indicated:

>    >     #include <concepts>

>    >     namespace std {
>    >       […]
>    >       // 23.4.4 [range.iter.ops], range iterator operations
>    >       namespace ranges {
>    >         […]
>    >         // 23.4.4.3 [range.iter.op.distance], ranges::distance
>    >         template<input_or_output_iterator I, sentinel_for<I> 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<I> S>
>    >           constexpr iter_difference_t<I> distance(const I& first, const S& last);
>    >         template<range R>
>    >           constexpr range_difference_t<R> distance(R&& r);
>    >         […]
>    >       }
>    >       […]
>    >     }

> 2. Modify 23.4.4.3 [range.iter.op.distance] as indicated:

>    >     template<input_or_output_iterator I, sentinel_for<I> S>
>    >       requires (!sized_sentinel_for<S, I>)
>    >         constexpr iter_difference_t<I> 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, r Returns the number of increments needed to get from `first` to `last`.

>    >     template<input_or_output_iterator I, sized_sentinel_for<I> S>
>    >       constexpr iter_difference_t<I> 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` model `same_as<S, I>`.

>    > > -? Effects: Returns `(last - first)`;

[2021-05-19 Tim updates wording]

The wording below removes the explicit precondition on the `sized_sentinel_for` overload 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-empty `std::vector<int> v`, `ranges::distance(v.begin(), v.cend())` is well-defined but `ranges::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 23.2 [iterator.synopsis], header `<iterator>` synopsis, as indicated:

   >     […]

   >     namespace std {
   >       […]
   >       // 23.4.4 [range.iter.ops], range iterator operations
   >       namespace ranges {
   >         […]
   >         // 23.4.4.3 [range.iter.op.distance], ranges::distance
   >         template<input_or_output_iterator I, sentinel_for<I> 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<I> S>
   >           constexpr iter_difference_t<I> distance(const I& first, const S& last);
   >         template<range R>
   >           constexpr range_difference_t<R> distance(R&& r);
   >         […]
   >       }
   >       […]
   >     }

2. Modify 23.4.4.3 [range.iter.op.distance] as indicated:

   >     template<input_or_output_iterator I, sentinel_for<I> S>
   >       requires (!sized_sentinel_for<S, I>)
   >         constexpr iter_difference_t<I> 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 from `first` to `last`.

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

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