Skip to content

Commit

Permalink
Speculatively implement LWG-3664 (#2522)
Browse files Browse the repository at this point in the history
to fix a regression when calling `ranges::distance` with array arguments which _should_ decay to pointers. While we're here, let's merge `distance(i, s)` overloads and use `if constexpr` dispatch instead of concept overloading.

Co-authored-by: Stephan T. Lavavej <[email protected]>
  • Loading branch information
CaseyCarter and StephanTLavavej committed Feb 7, 2022
1 parent ee6a79e commit bbd5dba
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 13 deletions.
30 changes: 17 additions & 13 deletions stl/inc/xutility
Original file line number Diff line number Diff line change
Expand Up @@ -2868,19 +2868,16 @@ namespace ranges {
public:
using _Not_quite_object::_Not_quite_object;

// clang-format off
template <input_or_output_iterator _It, sentinel_for<_It> _Se>
requires (!sized_sentinel_for<_Se, _It>)
_NODISCARD constexpr iter_difference_t<_It> operator()(_It _First, _Se _Last) const {
// clang-format on
_Adl_verify_range(_First, _Last);
return _Distance_unchecked(_Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)));
}

template <input_or_output_iterator _It, sized_sentinel_for<_It> _Se>
_NODISCARD constexpr iter_difference_t<_It> operator()(const _It& _First, const _Se& _Last) const
noexcept(noexcept(_Last - _First)) /* strengthened */ {
return _Last - _First;
template <class _It, sentinel_for<decay_t<_It>> _Se>
_NODISCARD constexpr iter_difference_t<decay_t<_It>> operator()(_It&& _Raw_first, _Se _Last) const
noexcept(_Nothrow_dist<_It, _Se>) /* strengthened */ {
if constexpr (sized_sentinel_for<_Se, decay_t<_It>>) {
return _Last - static_cast<const decay_t<_It>&>(_Raw_first); // Per LWG-3664
} else {
auto _First = _STD forward<_It>(_Raw_first);
_Adl_verify_range(_First, _Last);
return _Distance_unchecked(_Get_unwrapped(_STD move(_First)), _Get_unwrapped(_STD move(_Last)));
}
}

template <range _Rng>
Expand All @@ -2894,6 +2891,13 @@ namespace ranges {
}

private:
template <class _It, class _Se>
static constexpr bool _Nothrow_dist = false;

template <class _It, sized_sentinel_for<decay_t<_It>> _Se>
static constexpr bool _Nothrow_dist<_It, _Se> = noexcept(
_STD declval<_Se&>() - _STD declval<const decay_t<_It>&>());

template <class _It, class _Se>
_NODISCARD static constexpr iter_difference_t<_It> _Distance_unchecked(_It _First, const _Se _Last) noexcept(
noexcept(++_First != _Last)) {
Expand Down
46 changes: 46 additions & 0 deletions tests/std/tests/P0896R4_ranges_iterator_machinery/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2861,6 +2861,32 @@ namespace iter_ops {
}
};

template <class Element>
struct pointer_sentinel {
Element* ptr = nullptr;

pointer_sentinel() = default;
constexpr explicit pointer_sentinel(Element* const p) noexcept : ptr{p} {}

template <class T>
[[nodiscard]] constexpr bool operator==(T* that) const noexcept {
static_assert(std::same_as<T, Element>);
return ptr == that;
}

template <class T>
[[nodiscard]] friend constexpr std::ptrdiff_t operator-(T* x, const pointer_sentinel& y) noexcept {
static_assert(std::same_as<T, Element>);
return x - y.ptr;
}

template <class T>
[[nodiscard]] friend constexpr std::ptrdiff_t operator-(const pointer_sentinel& y, T* x) noexcept {
static_assert(std::same_as<T, Element>);
return y.ptr - x;
}
};

constexpr bool test_distance() {
using ranges::distance, ranges::size;
using std::iter_difference_t, std::same_as;
Expand Down Expand Up @@ -2974,6 +3000,26 @@ namespace iter_ops {
assert(r.t == expected);
}

{
// Call distance(i, s) with arrays which must be decayed to pointers.
// (This behavior was regressed by LWG-3392.)
int some_ints[] = {1, 2, 3};
assert(distance(some_ints, pointer_sentinel{some_ints + 1}) == 1);
STATIC_ASSERT(noexcept(distance(some_ints, pointer_sentinel{some_ints + 1})));
assert(distance(some_ints + 1, some_ints) == -1);
STATIC_ASSERT(noexcept(distance(some_ints + 1, some_ints)));
assert(distance(some_ints, some_ints) == 0);
STATIC_ASSERT(noexcept(distance(some_ints, some_ints)));

const auto& const_ints = some_ints;
assert(distance(const_ints, pointer_sentinel{const_ints + 1}) == 1);
STATIC_ASSERT(noexcept(distance(const_ints, pointer_sentinel{const_ints + 1})));
assert(distance(const_ints + 1, const_ints) == -1);
STATIC_ASSERT(noexcept(distance(const_ints + 1, const_ints)));
assert(distance(const_ints, const_ints) == 0);
STATIC_ASSERT(noexcept(distance(const_ints, const_ints)));
}

return true;
}
STATIC_ASSERT(test_distance());
Expand Down

0 comments on commit bbd5dba

Please sign in to comment.