]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Implement ranges::shift_left/right from P2440R1
authorPatrick Palka <ppalka@redhat.com>
Sun, 6 Jul 2025 18:49:34 +0000 (14:49 -0400)
committerPatrick Palka <ppalka@redhat.com>
Sun, 6 Jul 2025 18:49:34 +0000 (14:49 -0400)
The implementation is just a copy of std::shift_left/right with the
following changes:

 - check bidirectional_iterator instead of iterator_category
 - cope with __last being a distinct sentinel type
 - for shift_left, return the subrange {__first, X} instead of X
 - for shift_right, return the subrange {X, ranges::next(__first, __last)}
   instead of X
 - use the ranges:: versions of move_backward, move and iter_swap
 - don't bother std::move'ing any iterators, it's unnecessary since all
   iterators are forward, it's visually noisy, and in earlier versions
   of this patch it introduced subtle use-after-move bugs

In passing also use the __glibcxx_shift macro to guard the
std::shift_left/right implementations.

libstdc++-v3/ChangeLog:

* include/bits/ranges_algo.h (shift_left, shift_right): Guard
with __glibcxx_shift >= 201806L.
(ranges::__shift_left_fn, ranges::shift_left): Define for C++23.
(ranges::__shift_right_fn, ranges::shift_right): Likewise.
* include/bits/version.def (shift): Update for C++23.
* include/bits/version.h: Regenerate.
* src/c++23/std.cc.in: Add ranges::shift_left/right.
* testsuite/25_algorithms/shift_left/constrained.cc: New test,
based off of 1.cc.
* testsuite/25_algorithms/shift_right/constrained.cc: New test,
based off of 1.cc.

Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
libstdc++-v3/include/bits/ranges_algo.h
libstdc++-v3/include/bits/version.def
libstdc++-v3/include/bits/version.h
libstdc++-v3/src/c++23/std.cc.in
libstdc++-v3/testsuite/25_algorithms/shift_left/constrained.cc [new file with mode: 0644]
libstdc++-v3/testsuite/25_algorithms/shift_right/constrained.cc [new file with mode: 0644]

index cf369c5d4ddec1c1bb4f25f1099ae00d2161c180..9f8945a7133a4b6711296d4bfb6afd02916fef2e 100644 (file)
@@ -5218,6 +5218,7 @@ namespace ranges
 #endif // __glibcxx_ranges_fold
 } // namespace ranges
 
+#if __glibcxx_shift >= 201806L // C++ >= 20
   template<typename _ForwardIterator>
     constexpr _ForwardIterator
     shift_left(_ForwardIterator __first, _ForwardIterator __last,
@@ -5308,6 +5309,120 @@ namespace ranges
            }
        }
     }
+#endif
+
+namespace ranges
+{
+#if __glibcxx_shift >= 202202L // C++ >= 23
+  struct __shift_left_fn
+  {
+    template<permutable _Iter, sentinel_for<_Iter> _Sent>
+      constexpr subrange<_Iter>
+      operator()(_Iter __first, _Sent __last, iter_difference_t<_Iter> __n) const
+      {
+       __glibcxx_assert(__n >= 0);
+       if (__n == 0)
+         return {__first, ranges::next(__first, __last)};
+
+       auto __mid = ranges::next(__first, __n, __last);
+       if (__mid == __last)
+         return {__first, __first};
+       return {__first, ranges::move(__mid, __last, __first).out};
+      }
+
+    template<forward_range _Range>
+      requires permutable<iterator_t<_Range>>
+      constexpr borrowed_subrange_t<_Range>
+      operator()(_Range&& __r, range_difference_t<_Range> __n) const
+      { return (*this)(ranges::begin(__r), ranges::end(__r), __n); }
+  };
+
+  inline constexpr __shift_left_fn shift_left{};
+
+  struct __shift_right_fn
+  {
+    template<permutable _Iter, sentinel_for<_Iter> _Sent>
+      constexpr subrange<_Iter>
+      operator()(_Iter __first, _Sent __last, iter_difference_t<_Iter> __n) const
+      {
+       __glibcxx_assert(__n >= 0);
+       if (__n == 0)
+         return {__first, ranges::next(__first, __last)};
+
+       if constexpr (bidirectional_iterator<_Iter> && same_as<_Iter, _Sent>)
+         {
+           auto __mid = ranges::next(__last, -__n, __first);
+           if (__mid == __first)
+             return {__last, __last};
+
+           return {ranges::move_backward(__first, __mid, __last).out, __last};
+         }
+       else
+         {
+           auto __result = ranges::next(__first, __n, __last);
+           if (__result == __last)
+             return {__result, __result};
+
+           auto __dest_head = __first, __dest_tail = __result;
+           while (__dest_head != __result)
+             {
+               if (__dest_tail == __last)
+                 {
+                   // If we get here, then we must have
+                   //     2*n >= distance(__first, __last)
+                   // i.e. we are shifting out at least half of the range.  In
+                   // this case we can safely perform the shift with a single
+                   // move.
+                   auto __lasti = ranges::move(__first, __dest_head, __result).out;
+                   // __glibcxx_assert(__lasti == __last);
+                   return {__result, __lasti};
+                 }
+               ++__dest_head;
+               ++__dest_tail;
+             }
+
+           for (;;)
+             {
+               // At the start of each iteration of this outer loop, the range
+               // [__first, __result) contains those elements that after shifting
+               // the whole range right by __n, should end up in
+               // [__dest_head, __dest_tail) in order.
+
+               // The below inner loop swaps the elements of [__first, __result)
+               // and [__dest_head, __dest_tail), while simultaneously shifting
+               // the latter range by __n.
+               auto __cursor = __first;
+               while (__cursor != __result)
+                 {
+                   if (__dest_tail == __last)
+                     {
+                       // At this point the ranges [__first, result) and
+                       // [__dest_head, dest_tail) are disjoint, so we can safely
+                       // move the remaining elements.
+                       __dest_head = ranges::move(__cursor, __result, __dest_head).out;
+                       auto __lasti = ranges::move(__first, __cursor, __dest_head).out;
+                       // __glibcxx_assert(__lasti == __last);
+                       return {__result, __lasti};
+                     }
+                   ranges::iter_swap(__cursor, __dest_head);
+                   ++__dest_head;
+                   ++__dest_tail;
+                   ++__cursor;
+                 }
+             }
+         }
+      }
+
+    template<forward_range _Range>
+      requires permutable<iterator_t<_Range>>
+      constexpr borrowed_subrange_t<_Range>
+      operator()(_Range&& __r, range_difference_t<_Range> __n) const
+      { return (*this)(ranges::begin(__r), ranges::end(__r), __n); }
+  };
+
+  inline constexpr __shift_right_fn shift_right{};
+#endif // C++23
+} // namespace ranges
 
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
index 5d5758bf2033d904caee06fdf0493604a20ad029..64f8190d240b771a9c8f348bb0df6b9609f3a817 100644 (file)
@@ -1091,6 +1091,10 @@ ftms = {
 
 ftms = {
   name = shift;
+  values = {
+    v = 202202;
+    cxxmin = 23;
+  };
   values = {
     v = 201806;
     cxxmin = 20;
index 2b00e8419b3a8bec93df81ca2ecf76f7db75dc12..744246a9938af526ef4ec6075a48ffc985507aec 100644 (file)
 #undef __glibcxx_want_constexpr_utility
 
 #if !defined(__cpp_lib_shift)
-# if (__cplusplus >= 202002L)
+# if (__cplusplus >= 202100L)
+#  define __glibcxx_shift 202202L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_shift)
+#   define __cpp_lib_shift 202202L
+#  endif
+# elif (__cplusplus >= 202002L)
 #  define __glibcxx_shift 201806L
 #  if defined(__glibcxx_want_all) || defined(__glibcxx_want_shift)
 #   define __cpp_lib_shift 201806L
index 483b6c62443c8de0198efe7b7529b431bbd18667..86e6d88904b37f3e6ef030fa9bebb0cf660c4e68 100644 (file)
@@ -278,10 +278,14 @@ export namespace std
   }
   using std::shift_left;
   namespace ranges
-  {}
+  {
+    using std::ranges::shift_left;
+  }
   using std::shift_right;
   namespace ranges
-  {}
+  {
+    using std::ranges::shift_right;
+  }
   using std::sort;
   namespace ranges
   {
diff --git a/libstdc++-v3/testsuite/25_algorithms/shift_left/constrained.cc b/libstdc++-v3/testsuite/25_algorithms/shift_left/constrained.cc
new file mode 100644 (file)
index 0000000..73d0614
--- /dev/null
@@ -0,0 +1,105 @@
+// Copyright (C) 2020-2025 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-do run { target c++23 } }
+// This test is based on shift_left/1.cc.
+
+#include <algorithm>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+
+using __gnu_test::test_range;
+using __gnu_test::forward_iterator_wrapper;
+using __gnu_test::bidirectional_iterator_wrapper;
+using __gnu_test::random_access_iterator_wrapper;
+
+struct X
+{
+  int a = -1;
+  bool moved_from = false;
+
+  X() = default;
+
+  X(int a)
+    : a(a)
+  { }
+
+  X(const X&) = delete;
+  X& operator=(const X&) = delete;
+
+  X(X&& other)
+  {
+    if (this != &other)
+    *this = std::move(other);
+  }
+
+  X&
+  operator=(X&& other)
+  {
+    a = other.a;
+    other.moved_from = true;
+    moved_from = false;
+    return *this;
+  }
+};
+
+template<int N, template<typename> typename Wrapper>
+void
+test01()
+{
+  for (int n = 0; n < N+5; n++)
+    {
+      X x[N];
+      for (int i = 0; i < N; i++)
+       x[i] = X{i};
+      test_range<X, Wrapper> rx(x);
+      auto [first,out] = std::ranges::shift_left(rx.begin(), rx.end(), n);
+      VERIFY( first == rx.begin() );
+      if (n < N)
+       {
+         VERIFY( out.ptr == x+(N-n) );
+         for (int i = 0; i < N-n; i++)
+           {
+             VERIFY( x[i].a == n+i );
+             VERIFY( !x[i].moved_from );
+           }
+         for (int i = std::max(n, N-n); i < N; i++)
+           VERIFY( x[i].moved_from );
+       }
+      else
+       {
+         VERIFY( out.ptr == x );
+         for (int i = 0; i < N; i++)
+           {
+             VERIFY( x[i].a == i );
+             VERIFY( !x[i].moved_from );
+           }
+       }
+    }
+}
+
+int
+main()
+{
+  test01<23, forward_iterator_wrapper>();
+  test01<23, bidirectional_iterator_wrapper>();
+  test01<23, random_access_iterator_wrapper>();
+
+  test01<24, forward_iterator_wrapper>();
+  test01<24, bidirectional_iterator_wrapper>();
+  test01<24, random_access_iterator_wrapper>();
+}
diff --git a/libstdc++-v3/testsuite/25_algorithms/shift_right/constrained.cc b/libstdc++-v3/testsuite/25_algorithms/shift_right/constrained.cc
new file mode 100644 (file)
index 0000000..0d53707
--- /dev/null
@@ -0,0 +1,104 @@
+// Copyright (C) 2020-2025 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-do run { target c++23 } }
+// This test is based on shift_right/1.cc.
+
+#include <algorithm>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+
+using __gnu_test::test_range;
+using __gnu_test::forward_iterator_wrapper;
+using __gnu_test::bidirectional_iterator_wrapper;
+using __gnu_test::random_access_iterator_wrapper;
+
+struct X
+{
+  int a = -1;
+  bool moved_from = false;
+
+  X() = default;
+
+  X(int a)
+    : a(a)
+  { }
+
+  X(const X&) = delete;
+  X& operator=(const X&) = delete;
+
+  X(X&& other)
+  {
+    if (this != &other)
+    *this = std::move(other);
+  }
+
+  X&
+  operator=(X&& other)
+  {
+    a = other.a;
+    other.moved_from = true;
+    moved_from = false;
+    return *this;
+  }
+};
+
+template<int N, template<typename> typename Wrapper>
+void
+test01()
+{
+  for (int n = 0; n < N+5; n++)
+    {
+      X x[N];
+      for (int i = 0; i < N; i++)
+       x[i] = X(i);
+      test_range<X, Wrapper> rx(x);
+      auto [out,last] = std::ranges::shift_right(rx.begin(), rx.end(), n);
+      VERIFY( last == rx.end() );
+      if (n < N)
+       {
+         VERIFY( out.ptr == x+n );
+         for (int i = n; i < N; i++)
+           VERIFY( x[i].a == i-n );
+         for (int i = 0; i < std::min(n, N-n); i++)
+           VERIFY( x[i].moved_from );
+         for (int i = std::min(n, N-n); i < std::max(n, N-n); i++)
+           VERIFY( !x[i].moved_from );
+       }
+      else
+       {
+         VERIFY( out.ptr == x+N );
+         for (int i = 0; i < N; i++)
+           {
+             VERIFY( x[i].a == i );
+             VERIFY( !x[i].moved_from );
+           }
+       }
+    }
+}
+
+int
+main()
+{
+  test01<23, forward_iterator_wrapper>();
+  test01<23, bidirectional_iterator_wrapper>();
+  test01<23, random_access_iterator_wrapper>();
+
+  test01<24, forward_iterator_wrapper>();
+  test01<24, bidirectional_iterator_wrapper>();
+  test01<24, random_access_iterator_wrapper>();
+}