]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Implement LWG 4027 change to possibly-const-range [PR118083]
authorPatrick Palka <ppalka@redhat.com>
Tue, 25 Feb 2025 18:35:04 +0000 (13:35 -0500)
committerPatrick Palka <ppalka@redhat.com>
Tue, 25 Feb 2025 18:35:04 +0000 (13:35 -0500)
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 <jwakely@redhat.com>
libstdc++-v3/include/bits/ranges_base.h
libstdc++-v3/testsuite/std/ranges/access/cbegin.cc
libstdc++-v3/testsuite/std/ranges/access/cdata.cc
libstdc++-v3/testsuite/std/ranges/access/cend.cc
libstdc++-v3/testsuite/std/ranges/access/crbegin.cc
libstdc++-v3/testsuite/std/ranges/access/crend.cc
libstdc++-v3/testsuite/std/ranges/adaptors/join.cc
libstdc++-v3/testsuite/std/ranges/adaptors/take_while.cc
libstdc++-v3/testsuite/std/ranges/adaptors/transform.cc

index 4dcfbf66d51fce2d9859d6944545b0d3eabf0e56..28fe64a9e9da9ce7bcd9390e51a9a15ac394e581 100644 (file)
@@ -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;
index 5423e78242824284ac41e0bd8b3249ca67c27ccb..c85303d3e357778fda8809eb937a485363df56eb 100644 (file)
@@ -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();
 }
index 62c347be43d6a9c2797f6e6c0684f6adc5722c71..f474ab7ec99c2af32aa475ec096c34f2debcd48d 100644 (file)
@@ -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;
index 6194fe7d866b7774dc2c9fa987e0d4479bb9439b..6903c4558a10380afcbd9fd835608c5fc676fe73 100644 (file)
@@ -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; }
index 9a07f0b38743442a872ff9beb0e892525a125898..c283ee4e33c010ae2d1eb9ea40dc873f7a96b96a 100644 (file)
@@ -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) );
index 6f7dce28200ed09bc88ce112402ddca5520136fa..d4530e530e1ba296406188859737960955c6348e 100644 (file)
@@ -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)) );
 
index 9e1c526b4cadca564cdb3fe67e3edce43c7c86e6..2861115c22a098ab809709dd131a00d87baf65d1 100644 (file)
@@ -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);
 }
 
index 38757d27d48b4bf9f75bf42df3c6751b2064c095..f09919bedc49fab9f0ac7e6e569accd4cb320abc 100644 (file)
@@ -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);
 }
index 934d2f65dcf0e15efff3f75504110135010c2555..1788db1ce8d6737d4b1c5c11c735ac49b6343617 100644 (file)
@@ -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);
 }