3664. LWG 3392 broke std::ranges::distance(a, a+3)

Section: 24.4.4.3 [range.iter.op.distance] Status: C++23 Submitter: Arthur O'Dwyer Opened: 2022-01-23 Last modified: 2023-11-22

Priority: 2

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

View all issues with C++23 status.

Discussion:

Consider the use of std::ranges::distance(first, last) on a simple C array. This works fine with std::distance, but currently does not work with std::ranges::distance.

// godbolt link
#include <ranges>
#include <cassert>

int main() {
  int a[] = {1, 2, 3};
  assert(std::ranges::distance(a, a+3) == 3);
  assert(std::ranges::distance(a, a) == 0);
  assert(std::ranges::distance(a+3, a) == -3);
}

Before LWG 3392, we had a single iterator-pair overload:

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

which works fine for C pointers. After LWG 3392, we have two iterator-pair overloads:

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);

and unfortunately the one we want — distance(I first, S last) — is no longer viable because [with I=int*, S=int*], we have sized_sentinel_for<S, I> and so its constraints aren't satisfied. So we look at the other overload [with I=int[3], S=int[3]], but unfortunately its constraints aren't satisfied either, because int[3] is not an input_or_output_iterator.

[2022-01-30; Reflector poll]

Set priority to 2 after reflector poll.

Previous resolution [SUPERSEDED]:

This wording is relative to N4901.

[Drafting Note: Thanks to Casey Carter. Notice that sentinel_for<S, I> already implies and subsumes input_or_output_iterator<I>, so that constraint wasn't doing anything; personally I'd prefer to remove it for symmetry (and to save the environment). Otherwise you'll have people asking why one of the I's is constrained and the other isn't.]

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

    […]
    // 24.4.4.3 [range.iter.op.distance], ranges::distance
    template<classinput_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<classinput_or_output_iterator I, sized_sentinel_for<decay_t<I>> S>
      constexpr iter_difference_t<I> distance(const I& first, const S& last);
    […]
    
  2. Modify 24.4.4.3 [range.iter.op.distance] as indicated:

    template<classinput_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.

    -2- Effects: Increments first until last is reached and returns the number of increments.

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

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

[2022-02-16; Arthur and Casey provide improved wording]

[Kona 2022-11-08; Move to Ready]

[2023-02-13 Approved at February 2023 meeting in Issaquah. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4901.

[Drafting Note: Arthur thinks it's a bit "cute" of the Effects: element to static_cast from T(&)[N] to T* const& in the array case, but it does seem to do the right thing in all cases, and it saves us from having to use an if constexpr (is_array_v...) or something like that.]

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

    […]
    // 24.4.4.3 [range.iter.op.distance], ranges::distance
    template<classinput_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<classinput_or_output_iterator I, sized_sentinel_for<decay_t<I>> S>
      constexpr iter_difference_t<decay_t<I>> distance(const I&& first, const S& last);
    […]
    
  2. Modify 24.4.4.3 [range.iter.op.distance] as indicated:

    template<classinput_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.

    -2- Effects: Increments first until last is reached and returns the number of increments.

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

    -3- Effects: Equivalent to: return last - static_cast<const decay_t<I>&>(first);