[PATCH] libstdc++: Implement ranges::shift_left/right from P2440R1
Jonathan Wakely
jwakely@redhat.com
Tue Jul 1 10:13:03 GMT 2025
On Mon, 30 Jun 2025 at 22:16, Patrick Palka <ppalka@redhat.com> wrote:
>
> Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
>
> Btw, any thoughts on PR105611 which reports std::shift_left/right's use
> of the three-parameter version of ranges::next (on legacy iterators) is
> sketchy? Should we define a three-parameter std::__next analog for
> legacy iterators and use that instead?
I suppose we could do, but I find it hard to care about the corner
cases where ranges::next isn't optimal.
This patch for the ranges shift algos is OK for trunk (two comments
below, but they aren't asking for changes).
>
> -- >8 --
>
> The implementation is essentially a copy of std::shift_left/right
> (defined directly above) with the following changes:
>
> - check bidirectional_iterator instead of iterator_category
> - for shift_left, return {__first, X} instead of X
> - for shift_right, return {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, makes the code less readable, and introduces
> subtle use-after-move bugs in some code paths
>
> In passing guard the std::shift_left/right implementations with
> __glibcxx_shift as well.
>
> 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.
> * 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.
> ---
> libstdc++-v3/include/bits/ranges_algo.h | 121 ++++++++++++++++++
> libstdc++-v3/include/bits/version.def | 4 +
> libstdc++-v3/include/bits/version.h | 7 +-
> .../25_algorithms/shift_left/constrained.cc | 105 +++++++++++++++
> .../25_algorithms/shift_right/constrained.cc | 104 +++++++++++++++
> 5 files changed, 340 insertions(+), 1 deletion(-)
> create mode 100644 libstdc++-v3/testsuite/25_algorithms/shift_left/constrained.cc
> create mode 100644 libstdc++-v3/testsuite/25_algorithms/shift_right/constrained.cc
>
> diff --git a/libstdc++-v3/include/bits/ranges_algo.h b/libstdc++-v3/include/bits/ranges_algo.h
> index 5aca6e8d864d..9bb84332d8e1 100644
> --- a/libstdc++-v3/include/bits/ranges_algo.h
> +++ b/libstdc++-v3/include/bits/ranges_algo.h
> @@ -4261,6 +4261,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,
> @@ -4351,6 +4352,126 @@ 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
> + {
> + _Iter __lasti = ranges::next(__first, __last);
> + return {_S_impl(__first, __lasti, __n), __lasti};
> + }
> +
> + template<typename _Iter>
> + static constexpr _Iter
> + _S_impl(_Iter __first, _Iter __last, iter_difference_t<_Iter> __n)
> + {
> + __glibcxx_assert(__n >= 0);
> + if (__n == 0)
> + return __first;
> +
> + if constexpr (bidirectional_iterator<_Iter>)
> + {
> + auto __mid = ranges::next(__last, -__n, __first);
I wondered if using ranges::prev(__last, __n, __first) would avoid
possible overflow for n == numeric_limits::min() but we just do -n in
ranges::prev too so it's identical anyway.
> + if (__mid == __first)
> + return __last;
> +
> + return ranges::move_backward(__first, __mid, __last).out;
> + }
> + else
> + {
> + auto __result = ranges::next(__first, __n, __last);
> + if (__result == __last)
> + return __last;
> +
> + 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.
> + ranges::move(__first, __dest_head, __result);
> + return __result;
> + }
> + ++__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;
> + ranges::move(__first, __cursor, __dest_head);
> + return __result;
> + }
> + 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
> diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
> index 880586e91260..917806e9a9a8 100644
> --- a/libstdc++-v3/include/bits/version.def
> +++ b/libstdc++-v3/include/bits/version.def
> @@ -1091,6 +1091,10 @@ ftms = {
>
> ftms = {
> name = shift;
> + values = {
> + v = 202202;
> + cxxmin = 23;
> + };
> values = {
> v = 201806;
> cxxmin = 20;
> diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
> index 4300adb22762..ca3b062dfc61 100644
> --- a/libstdc++-v3/include/bits/version.h
> +++ b/libstdc++-v3/include/bits/version.h
> @@ -1224,7 +1224,12 @@
> #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
> 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
> index 000000000000..73d061439885
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/25_algorithms/shift_left/constrained.cc
> @@ -0,0 +1,105 @@
> +// Copyright (C) 2020-2025 Free Software Foundation, Inc.
The new tests are based on existing ones, so the copyright notice is correct.
> +//
> +// 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
> index 000000000000..0d53707237e4
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/25_algorithms/shift_right/constrained.cc
> @@ -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>();
> +}
> --
> 2.50.0.131.gcf6f63ea6b
>
More information about the Libstdc++
mailing list