Title
LWG 3392 broke std::ranges::distance(a, a+3)
Status
c++23
Section
[range.iter.op.distance]
Submitter
Arthur O'Dwyer

Created on 2022-01-23.00:00:00 last changed 4 months ago

Messages

Date: 2023-02-13.10:17:57

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 [iterator.synopsis], header <iterator> synopsis, as indicated:

    […]
    // [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 [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);

Date: 2023-02-13.00:00:00

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

Date: 2022-11-10.23:33:23

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

Date: 2022-02-15.00:00:00

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

Date: 2022-01-15.00:00:00

[ 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 [iterator.synopsis], header <iterator> synopsis, as indicated:

    […]
    // [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 [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;

Date: 2022-01-23.00:00:00

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.

History
Date User Action Args
2023-11-22 15:47:43adminsetstatus: wp -> c++23
2023-02-13 10:17:57adminsetmessages: + msg13351
2023-02-13 10:17:57adminsetstatus: voting -> wp
2023-02-06 15:33:48adminsetstatus: ready -> voting
2022-11-10 23:33:23adminsetmessages: + msg13007
2022-11-10 23:33:23adminsetstatus: new -> ready
2022-02-18 16:36:17adminsetmessages: + msg12380
2022-01-30 17:05:36adminsetmessages: + msg12332
2022-01-23 16:07:48adminsetmessages: + msg12280
2022-01-23 00:00:00admincreate