https://gcc.gnu.org/g:1b9e4fe2ff5f4711406cdcf0e6e183b247d9f42b
commit r15-7698-g1b9e4fe2ff5f4711406cdcf0e6e183b247d9f42b Author: Patrick Palka <ppa...@redhat.com> Date: Tue Feb 25 13:35:04 2025 -0500 libstdc++: Implement LWG 4027 change to possibly-const-range [PR118083] LWG 4027 effectively makes the const range access CPOs ranges::cfoo behave more consistently across C++23 and C++20 (pre-P2278R4) and also more consistently with the std::cfoo range accessors, as the below testcase adjustments demonstrate (which mostly consist of reverting workarounds added by r14-3771-gf12e26f3496275 and r13-7186-g0d94c6df183375). In passing fix PR118083 which reports that the input_range constraint on possibly-const-range is missing in our implementation. A consequence of this is that the const range access CPOs now consistently reject a non-range argument, and so in some our of tests we need to introduce otherwise unused begin/end members. PR libstdc++/118083 libstdc++-v3/ChangeLog: * include/bits/ranges_base.h (ranges::__access::__possibly_const_range): Adjust logic as per LWG 4027. Add missing input_range constraint. * testsuite/std/ranges/access/cbegin.cc (test05): Verify LWG 4027 testcases. * testsuite/std/ranges/access/cdata.cc: Adjust, simplify and consolidate some tests after the above. * testsuite/std/ranges/access/cend.cc: Likewise. * testsuite/std/ranges/access/crbegin.cc: Likewise. * testsuite/std/ranges/access/crend.cc: Likewise. * testsuite/std/ranges/adaptors/join.cc: Likewise. * testsuite/std/ranges/adaptors/take_while.cc: Likewise. * testsuite/std/ranges/adaptors/transform.cc: Likewise. Reviewed-by: Jonathan Wakely <jwak...@redhat.com> Diff: --- libstdc++-v3/include/bits/ranges_base.h | 4 +- libstdc++-v3/testsuite/std/ranges/access/cbegin.cc | 17 +++++++++ libstdc++-v3/testsuite/std/ranges/access/cdata.cc | 21 ++++++----- libstdc++-v3/testsuite/std/ranges/access/cend.cc | 30 ++------------- .../testsuite/std/ranges/access/crbegin.cc | 43 +++++----------------- libstdc++-v3/testsuite/std/ranges/access/crend.cc | 20 ++++++---- libstdc++-v3/testsuite/std/ranges/adaptors/join.cc | 8 ++-- .../testsuite/std/ranges/adaptors/take_while.cc | 2 - .../testsuite/std/ranges/adaptors/transform.cc | 4 -- 9 files changed, 59 insertions(+), 90 deletions(-) diff --git a/libstdc++-v3/include/bits/ranges_base.h b/libstdc++-v3/include/bits/ranges_base.h index 4dcfbf66d51f..28fe64a9e9da 100644 --- a/libstdc++-v3/include/bits/ranges_base.h +++ b/libstdc++-v3/include/bits/ranges_base.h @@ -642,11 +642,11 @@ namespace ranges namespace __access { #if __glibcxx_ranges_as_const // >= C++23 - template<typename _Range> + template<input_range _Range> constexpr auto& __possibly_const_range(_Range& __r) noexcept { - if constexpr (constant_range<const _Range> && !constant_range<_Range>) + if constexpr (input_range<const _Range>) return const_cast<const _Range&>(__r); else return __r; diff --git a/libstdc++-v3/testsuite/std/ranges/access/cbegin.cc b/libstdc++-v3/testsuite/std/ranges/access/cbegin.cc index 5423e7824282..c85303d3e357 100644 --- a/libstdc++-v3/testsuite/std/ranges/access/cbegin.cc +++ b/libstdc++-v3/testsuite/std/ranges/access/cbegin.cc @@ -116,10 +116,27 @@ test04() VERIFY(std::ranges::cbegin(std::move(c)) == std::ranges::begin(c)); } +void +test05() +{ + // LWG 4027 - possibly-const-range should prefer returning const R& + auto r = std::views::single(0) + | std::views::transform([](int) { return 0; }); + using C1 = decltype(std::ranges::cbegin(r)); + using C1 = decltype(std::cbegin(r)); + + [] (auto x) { + auto r = std::views::single(x) | std::views::lazy_split(0); + static_assert(!requires { (*std::ranges::cbegin(r)).front() = 42; }); + static_assert(!requires { (*std::cbegin(r)).front() = 42; }); + }(0); +} + int main() { test01(); test03(); test04(); + test05(); } diff --git a/libstdc++-v3/testsuite/std/ranges/access/cdata.cc b/libstdc++-v3/testsuite/std/ranges/access/cdata.cc index 62c347be43d6..f474ab7ec99c 100644 --- a/libstdc++-v3/testsuite/std/ranges/access/cdata.cc +++ b/libstdc++-v3/testsuite/std/ranges/access/cdata.cc @@ -34,20 +34,21 @@ test01() { int i = 0; int j = 0; + +#if __cpp_lib_ranges_as_const + // These overloads mean that range<R> and range<const R> are satisfied. + const int* begin() const { throw; } + const int* end() const { throw; } +#endif + int* data() { return &j; } const R* data() const noexcept { return nullptr; } }; static_assert( has_cdata<R&> ); static_assert( has_cdata<const R&> ); R r; -#if ! __cpp_lib_ranges_as_const VERIFY( std::ranges::cdata(r) == (R*)nullptr ); static_assert( noexcept(std::ranges::cdata(r)) ); -#else - // constant_range<const R> is not satisfied, so cdata(r) == data(r). - VERIFY( std::ranges::cdata(r) == &r.j ); - static_assert( ! noexcept(std::ranges::cdata(r)) ); -#endif const R& c = r; VERIFY( std::ranges::cdata(c) == (R*)nullptr ); static_assert( noexcept(std::ranges::cdata(c)) ); @@ -58,11 +59,11 @@ test01() struct R2 { +#if __cpp_lib_ranges_as_const // These overloads mean that range<R2> and range<const R2> are satisfied. - int* begin(); - int* end(); - const int* begin() const; - const int* end() const; + const int* begin() const { throw; } + const int* end() const { throw; } +#endif int i = 0; int j = 0; diff --git a/libstdc++-v3/testsuite/std/ranges/access/cend.cc b/libstdc++-v3/testsuite/std/ranges/access/cend.cc index 6194fe7d866b..6903c4558a10 100644 --- a/libstdc++-v3/testsuite/std/ranges/access/cend.cc +++ b/libstdc++-v3/testsuite/std/ranges/access/cend.cc @@ -52,15 +52,6 @@ struct R friend const int* end(const R&& r) noexcept { return r.a + 3; } }; -#if __cpp_lib_ranges_as_const -struct R2 : R -{ - // This overload means constant_range<const R2> will be satisfied: - friend const int* begin(const R2&) noexcept; - friend const int* end(const R2& r2) noexcept { return r2.a + 2; } -}; -#endif - struct RV // view on an R { R& r; @@ -79,26 +70,11 @@ test03() { R r; const R& c = r; -#if ! __cpp_lib_ranges_as_const VERIFY( std::ranges::cend(r) == std::ranges::end(c) ); -#else - // constant_range<const R> is not satisfied, so cend(r) == end(r) instead. - VERIFY( std::ranges::cend(r) == std::ranges::end(r) ); - R2 r2; - const R& c2 = r2; - // But constant_range<const R2> is satisfied, so cend(r2) == end(c2). - VERIFY( std::ranges::cend(r2) == std::ranges::end(c2) ); - VERIFY( std::ranges::cend(r2) == std::ranges::end((const R&)c2) ); -#endif VERIFY( std::ranges::cend(c) == std::ranges::end(c) ); RV v{r}; -#if ! __cpp_lib_ranges_as_const VERIFY( std::ranges::cend(std::move(v)) == std::ranges::end(c) ); -#else - // constant_range<RV> is already satisfied, so cend(v) == end(r) instead. - VERIFY( std::ranges::cend(std::move(v)) == std::ranges::end(r) ); -#endif const RV cv{r}; VERIFY( std::ranges::cend(std::move(cv)) == std::ranges::end(c) ); @@ -107,7 +83,7 @@ test03() struct RR { short s = 0; - long l = 0; + short l = 0; int a[4] = { 0, 1, 2, 3 }; const void* begin() const; // return type not an iterator @@ -115,8 +91,8 @@ struct RR friend int* end(RR&) { throw 1; } short* end() noexcept { return &s; } - friend const long* begin(const RR&) noexcept; - const long* end() const { return &l; } + friend const short* begin(const RR&) noexcept; + const short* end() const { return &l; } friend int* begin(RR&&) noexcept; friend int* end(RR&& r) { return r.a + 1; } diff --git a/libstdc++-v3/testsuite/std/ranges/access/crbegin.cc b/libstdc++-v3/testsuite/std/ranges/access/crbegin.cc index 9a07f0b38743..c283ee4e33c0 100644 --- a/libstdc++-v3/testsuite/std/ranges/access/crbegin.cc +++ b/libstdc++-v3/testsuite/std/ranges/access/crbegin.cc @@ -28,6 +28,11 @@ struct R1 int i = 0; int j = 0; +#if __cpp_lib_ranges_as_const + const int *begin() const; + const int *end() const; +#endif + const int* rbegin() const { return &i; } friend const int* rbegin(const R1&& r) { return &r.j; } }; @@ -36,6 +41,11 @@ struct R1V // view on an R1 { R1& r; +#if __cpp_lib_ranges_as_const + const int *begin() const; + const int *end() const; +#endif + friend const long* rbegin(R1V&) { return nullptr; } friend const int* rbegin(const R1V& rv) noexcept { return rv.r.rbegin(); } }; @@ -43,26 +53,6 @@ struct R1V // view on an R1 // Allow ranges::end to work with R1V&& template<> constexpr bool std::ranges::enable_borrowed_range<R1V> = true; -#if __cpp_lib_ranges_as_const -struct R1VC // view on an R1 -{ - R1& r; - - friend const long* rbegin(R1VC&); // this is not defined - friend const int* rbegin(const R1VC& rv) noexcept { return rv.r.rbegin(); } - - // The following ensure that the following are satisfied: - // constant_range<const R1VC> && ! constant_range<R1VC> - friend int* begin(R1VC&); - friend int* end(R1VC&); - friend const int* begin(const R1VC&); - friend const int* end(const R1VC&); -}; - -// Allow ranges::end to work with R1VC&& -template<> constexpr bool std::ranges::enable_borrowed_range<R1VC> = true; -#endif - void test01() { @@ -72,21 +62,8 @@ test01() VERIFY( std::ranges::crbegin(c) == std::ranges::rbegin(c) ); R1V v{r}; -#if ! __cpp_lib_ranges_as_const VERIFY( std::ranges::crbegin(v) == std::ranges::rbegin(c) ); VERIFY( std::ranges::crbegin(std::move(v)) == std::ranges::rbegin(c) ); -#else - // constant_range<const R1V> is not satisfied, so crbegin(v) == rbegin(v). - VERIFY( std::ranges::crbegin(v) == (long*)nullptr ); - VERIFY( std::ranges::crbegin(std::move(v)) == (long*)nullptr ); - R1VC v2{r}; - // But constant_range<const R1VC> is satisfied: - VERIFY( std::ranges::crbegin(v2) == std::ranges::rbegin(c) ); - VERIFY( std::ranges::crbegin(std::move(v2)) == std::ranges::rbegin(c) ); - const R1VC cv2{r}; - VERIFY( std::ranges::crbegin(cv2) == std::ranges::rbegin(c) ); - VERIFY( std::ranges::crbegin(std::move(cv2)) == std::ranges::rbegin(c) ); -#endif const R1V cv{r}; VERIFY( std::ranges::crbegin(cv) == std::ranges::rbegin(c) ); diff --git a/libstdc++-v3/testsuite/std/ranges/access/crend.cc b/libstdc++-v3/testsuite/std/ranges/access/crend.cc index 6f7dce28200e..d4530e530e1b 100644 --- a/libstdc++-v3/testsuite/std/ranges/access/crend.cc +++ b/libstdc++-v3/testsuite/std/ranges/access/crend.cc @@ -28,6 +28,11 @@ struct R1 int i = 0; int j = 0; +#if __cpp_lib_ranges_as_const + const int *begin() const; + const int *end() const; +#endif + constexpr const int* rbegin() const { return &i; } constexpr const int* rend() const { return &i + 1; } friend constexpr const int* rbegin(const R1&& r) { return &r.j; } @@ -78,6 +83,11 @@ struct R3 { int i = 0; +#if __cpp_lib_ranges_as_const + const int *begin() const; + const int *end() const; +#endif + const int* rbegin() const noexcept { return &i + 1; } const long* rend() const noexcept { return nullptr; } // not a sentinel for rbegin() @@ -89,9 +99,11 @@ struct R4 { int i = 0; +#if __cpp_lib_ranges_as_const // These members mean that range<R4> and range<const R4> are satisfied. const short* begin() const { return 0; } const short* end() const { return 0; } +#endif const int* rbegin() const noexcept { return &i + 1; } const long* rend() const noexcept { return nullptr; } // not a sentinel for rbegin() @@ -105,16 +117,8 @@ test03() { R3 r; const R3& c = r; -#if ! __cpp_lib_ranges_as_const VERIFY( std::ranges::crend(r) == std::ranges::rend(c) ); static_assert( !noexcept(std::ranges::crend(r)) ); -#else - // constant_range<const R3> is not satisfied, so crend(r) is equivalent - // to const_sentinel{rend(r)}, which is ill-formed because range<R3> - // is not satisfied. - static_assert( not std::ranges::range<R3> ); - static_assert( not std::ranges::range<const R3> ); -#endif VERIFY( std::ranges::crend(c) == std::ranges::rend(c) ); static_assert( !noexcept(std::ranges::crend(c)) ); diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/join.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/join.cc index 9e1c526b4cad..2861115c22a0 100644 --- a/libstdc++-v3/testsuite/std/ranges/adaptors/join.cc +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/join.cc @@ -113,15 +113,15 @@ test06() // Verify that _Iterator<false> is implicitly convertible to _Iterator<true>. static_assert(!std::same_as<decltype(ranges::begin(v)), - decltype(std::as_const(v).begin())>); - auto a = std::as_const(v).begin(); + decltype(ranges::cbegin(v))>); + auto a = std::cbegin(v); a = ranges::begin(v); // Verify that _Sentinel<false> is implicitly convertible to _Sentinel<true>. static_assert(!ranges::common_range<decltype(v)>); static_assert(!std::same_as<decltype(ranges::end(v)), - decltype(std::as_const(v).end())>); - auto b = std::as_const(v).end(); + decltype(ranges::cend(v))>); + auto b = ranges::cend(v); b = ranges::end(v); } diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/take_while.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/take_while.cc index 38757d27d48b..f09919bedc49 100644 --- a/libstdc++-v3/testsuite/std/ranges/adaptors/take_while.cc +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/take_while.cc @@ -63,10 +63,8 @@ test03() // Verify that _Sentinel<false> is implicitly convertible to _Sentinel<true>. static_assert(!ranges::common_range<decltype(v)>); -#if ! __cpp_lib_ranges_as_const static_assert(!std::same_as<decltype(ranges::end(v)), decltype(ranges::cend(v))>); -#endif auto b = ranges::cend(v); b = ranges::end(v); } diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc index 934d2f65dcf0..1788db1ce8d6 100644 --- a/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc +++ b/libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc @@ -107,20 +107,16 @@ test05() auto r = ranges::subrange{i, std::default_sentinel}; auto v = r | views::transform(std::negate{}); -#if ! __cpp_lib_ranges_as_const // Verify that _Iterator<false> is implicitly convertible to _Iterator<true>. static_assert(!std::same_as<decltype(ranges::begin(v)), decltype(ranges::cbegin(v))>); -#endif auto a = ranges::cbegin(v); a = ranges::begin(v); -#if ! __cpp_lib_ranges_as_const // Verify that _Sentinel<false> is implicitly convertible to _Sentinel<true>. static_assert(!ranges::common_range<decltype(v)>); static_assert(!std::same_as<decltype(ranges::end(v)), decltype(ranges::cend(v))>); -#endif auto b = ranges::cend(v); b = ranges::end(v); }