On Mon, 30 Jun 2025 at 22:16, Patrick Palka <ppa...@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 >