On Fri, 27 Jun 2025 at 15:26, Patrick Palka <ppa...@redhat.com> wrote:
>
> On Fri, 27 Jun 2025, Jonathan Wakely wrote:
>
> > On 26/06/25 22:25 -0400, Patrick Palka wrote:
> > >     PR libstdc++/100795
> > >
> > > libstdc++-v3/ChangeLog:
> > >
> > >     * include/bits/ranges_algo.h (__detail::__find_if_not_n): New,
> > >     based on the stl_algo.h implementation.
> > >     (__detail::__stable_partition_adaptive): Likewise.
> > >     (__stable_partition_fn::operator()): Reimplement in terms of
> > >     the above.
> > >     * testsuite/25_algorithms/stable_partition/constrained.cc
> > >     (test03): New test.
> > > ---
> > > libstdc++-v3/include/bits/ranges_algo.h       | 106 +++++++++++++++++-
> > > .../stable_partition/constrained.cc           |  26 +++++
> > > 2 files changed, 127 insertions(+), 5 deletions(-)
> > >
> > > diff --git a/libstdc++-v3/include/bits/ranges_algo.h
> > > b/libstdc++-v3/include/bits/ranges_algo.h
> > > index 7dfd4e7ed64c..a9924cd9c49e 100644
> > > --- a/libstdc++-v3/include/bits/ranges_algo.h
> > > +++ b/libstdc++-v3/include/bits/ranges_algo.h
> > > @@ -3133,6 +3133,81 @@ namespace ranges
> > >   inline constexpr __partition_fn partition{};
> > >
> > > #if _GLIBCXX_HOSTED
> > > +  namespace __detail
> > > +  {
> > > +    /// Like find_if_not(), but uses and updates a count of the
> > > +    /// remaining range length instead of comparing against an end
> > > +    /// iterator.
> > > +    template<typename _Iter, typename _Pred, typename _Distance>
> > > +      constexpr _Iter
> > > +      __find_if_not_n(_Iter __first, _Distance& __len, _Pred __pred)
> > > +      {
> > > +   for (; __len; --__len,  (void) ++__first)
> > > +     if (!__pred(*__first))
> > > +       break;
> > > +   return __first;
> > > +      }
> > > +
> > > +    template<typename _Iter, typename _Sent, typename _Pointer,
> > > +        typename _Pred, typename _Distance>
> > > +      _GLIBCXX26_CONSTEXPR
> > > +      subrange<_Iter>
> > > +      __stable_partition_adaptive(_Iter __first, _Sent __last,
> > > +                             _Pred __pred, _Distance __len,
> > > +                             _Pointer __buffer,
> > > +                             _Distance __buffer_size)
> > > +      {
> > > +   if (__len == 1)
> > > +     return {__first, ranges::next(__first, 1)};
> > > +
> > > +   if (__len <= __buffer_size)
> > > +     {
> > > +       _Iter __result1 = __first;
> > > +       _Pointer __result2 = __buffer;
> > > +
> > > +       // The precondition guarantees that !__pred(__first), so
> > > +       // move that element to the buffer before starting the loop.
> > > +       // This ensures that we only call __pred once per element.
> > > +       *__result2 = ranges::iter_move(__first);
> > > +       ++__result2;
> > > +       ++__first;
> > > +       for (; __first != __last; ++__first)
> > > +         if (__pred(*__first))
> > > +           {
> > > +             *__result1 = ranges::iter_move(__first);
> > > +             ++__result1;
> > > +           }
> > > +         else
> > > +           {
> > > +             *__result2 = ranges::iter_move(__first);
> > > +             ++__result2;
> > > +           }
> > > +
> > > +       ranges::move(__buffer, __result2, __result1);
> > > +       return {__result1, __first};
> > > +     }
> > > +
> > > +   _Iter __middle = __first;
> > > +   ranges::advance(__middle, __len / 2);
> > > +   _Iter __left_split
> > > +     = __detail::__stable_partition_adaptive(__first, __middle, __pred,
> > > +                                             __len / 2, __buffer,
> > > +                                             __buffer_size).begin();
> > > +
> > > +   // Advance past true-predicate values to satisfy this
> > > +   // function's preconditions.
> > > +   _Distance __right_len = __len - __len / 2;
> > > +   _Iter __right_split = __detail::__find_if_not_n(__middle, __right_len,
> > > __pred);
> > > +
> > > +   if (__right_len)
> > > +     __right_split
> > > +       = __detail::__stable_partition_adaptive(__right_split, __last,
> > > __pred,
> > > +                                               __right_len, __buffer,
> > > __buffer_size).begin();
> > > +
> > > +   return ranges::rotate(__left_split, __middle, __right_split);
> > > +      }
> > > +  } // namespace __detail
> > > +
> > >   struct __stable_partition_fn
> > >   {
> > >     template<bidirectional_iterator _Iter, sentinel_for<_Iter> _Sent,
> > > @@ -3144,11 +3219,32 @@ namespace ranges
> > >       operator()(_Iter __first, _Sent __last,
> > >              _Pred __pred, _Proj __proj = {}) const
> > >       {
> > > -   auto __lasti = ranges::next(__first, __last);
> > > -   auto __middle
> > > -     = std::stable_partition(std::move(__first), __lasti,
> > > -                             __detail::__make_pred_proj(__pred, __proj));
> > > -   return {std::move(__middle), std::move(__lasti)};
> > > +   auto __pred_proj = __detail::__make_pred_proj(__pred, __proj);
> > > +   __first = ranges::find_if_not(__first, __last, __pred_proj);
> >
> > Does this end up going through another layer of
> > invoke(pred, invoke(proj, *i)) inside ranges::find_if_not?
> > Hopeuflly with the recent _Pred_proj changes that will get inlined,
> > but is there any reason to not just use:
> >
> >       __first = ranges::find_if_not(__first, __last, __pred, __proj);
> >
> > here, and then use __pred_proj for the __stable_partition_adaptive
> > calls below?
>
> Good catch, that works nicely here (and I think this is the only such
> occurrence of passing a composite predicate/projection to a ranges algo).
> Fixed along with the accidentally Doxygen-style comment (and made
> __stable_partition_adaptive uncoditionally constexpr), like so?

OK for trunk, thanks.

Would it make sense to add a deleted overload of __make_pred_proj to
make it ill-formed to wrap a _Pred_proj in another _Pred_proj?


> Thanks a lot for the reviews!
>
> -- >8 --
>
> Subject: [PATCH 5/8] libstdc++: Directly implement ranges::stable_partition
>  [PR100795]
>
>         PR libstdc++/100795
>
> libstdc++-v3/ChangeLog:
>
>         * include/bits/ranges_algo.h (__detail::__find_if_not_n): New,
>         based on the stl_algo.h implementation.
>         (__detail::__stable_partition_adaptive): Likewise.
>         (__stable_partition_fn::operator()): Reimplement in terms of
>         the above.
>         * testsuite/25_algorithms/stable_partition/constrained.cc
>         (test03): New test.
> ---
>  libstdc++-v3/include/bits/ranges_algo.h       | 106 +++++++++++++++++-
>  .../stable_partition/constrained.cc           |  26 +++++
>  2 files changed, 127 insertions(+), 5 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/ranges_algo.h 
> b/libstdc++-v3/include/bits/ranges_algo.h
> index d0d3d92bd52c..a214e03d7b88 100644
> --- a/libstdc++-v3/include/bits/ranges_algo.h
> +++ b/libstdc++-v3/include/bits/ranges_algo.h
> @@ -3133,6 +3133,80 @@ namespace ranges
>    inline constexpr __partition_fn partition{};
>
>  #if _GLIBCXX_HOSTED
> +  namespace __detail
> +  {
> +    // Like find_if_not(), but uses and updates a count of the
> +    // remaining range length instead of comparing against an end
> +    // iterator.
> +    template<typename _Iter, typename _Pred, typename _Distance>
> +      constexpr _Iter
> +      __find_if_not_n(_Iter __first, _Distance& __len, _Pred __pred)
> +      {
> +       for (; __len; --__len,  (void) ++__first)
> +         if (!__pred(*__first))
> +           break;
> +       return __first;
> +      }
> +
> +    template<typename _Iter, typename _Sent, typename _Pointer,
> +            typename _Pred, typename _Distance>
> +      constexpr subrange<_Iter>
> +      __stable_partition_adaptive(_Iter __first, _Sent __last,
> +                                 _Pred __pred, _Distance __len,
> +                                 _Pointer __buffer,
> +                                 _Distance __buffer_size)
> +      {
> +       if (__len == 1)
> +         return {__first, ranges::next(__first, 1)};
> +
> +       if (__len <= __buffer_size)
> +         {
> +           _Iter __result1 = __first;
> +           _Pointer __result2 = __buffer;
> +
> +           // The precondition guarantees that !__pred(__first), so
> +           // move that element to the buffer before starting the loop.
> +           // This ensures that we only call __pred once per element.
> +           *__result2 = ranges::iter_move(__first);
> +           ++__result2;
> +           ++__first;
> +           for (; __first != __last; ++__first)
> +             if (__pred(*__first))
> +               {
> +                 *__result1 = ranges::iter_move(__first);
> +                 ++__result1;
> +               }
> +             else
> +               {
> +                 *__result2 = ranges::iter_move(__first);
> +                 ++__result2;
> +               }
> +
> +           ranges::move(__buffer, __result2, __result1);
> +           return {__result1, __first};
> +         }
> +
> +       _Iter __middle = __first;
> +       ranges::advance(__middle, __len / 2);
> +       _Iter __left_split
> +         = __detail::__stable_partition_adaptive(__first, __middle, __pred,
> +                                                 __len / 2, __buffer,
> +                                                 __buffer_size).begin();
> +
> +       // Advance past true-predicate values to satisfy this
> +       // function's preconditions.
> +       _Distance __right_len = __len - __len / 2;
> +       _Iter __right_split = __detail::__find_if_not_n(__middle, 
> __right_len, __pred);
> +
> +       if (__right_len)
> +         __right_split
> +           = __detail::__stable_partition_adaptive(__right_split, __last, 
> __pred,
> +                                                   __right_len, __buffer, 
> __buffer_size).begin();
> +
> +       return ranges::rotate(__left_split, __middle, __right_split);
> +      }
> +  } // namespace __detail
> +
>    struct __stable_partition_fn
>    {
>      template<bidirectional_iterator _Iter, sentinel_for<_Iter> _Sent,
> @@ -3144,11 +3218,33 @@ namespace ranges
>        operator()(_Iter __first, _Sent __last,
>                  _Pred __pred, _Proj __proj = {}) const
>        {
> -       auto __lasti = ranges::next(__first, __last);
> -       auto __middle
> -         = std::stable_partition(std::move(__first), __lasti,
> -                                 __detail::__make_pred_proj(__pred, __proj));
> -       return {std::move(__middle), std::move(__lasti)};
> +       __first = ranges::find_if_not(__first, __last, __pred, __proj);
> +
> +       if (__first == __last)
> +         return {__first, __first};
> +
> +       using _DistanceType = iter_difference_t<_Iter>;
> +       const _DistanceType __len = ranges::distance(__first, __last);
> +
> +       auto __pred_proj = __detail::__make_pred_proj(__pred, __proj);
> +
> +#if __glibcxx_constexpr_algorithms >= 202306L // >= C++26
> +       if consteval {
> +         // Simulate a _Temporary_buffer of length 1:
> +         iter_value_t<_Iter> __buf = ranges::iter_move(__first);
> +         *__first = std::move(__buf);
> +         return __detail::__stable_partition_adaptive(__first, __last,
> +                                                      __pred_proj,
> +                                                      __len, &__buf,
> +                                                      _DistanceType(1));
> +       }
> +#endif
> +
> +       _Temporary_buffer<_Iter, iter_value_t<_Iter>> __buf(__first, 
> ptrdiff_t(__len));
> +       return __detail::__stable_partition_adaptive(__first, __last,
> +                                                    __pred_proj,
> +                                                    __len, __buf.begin(),
> +                                                    
> _DistanceType(__buf.size()));
>        }
>
>      template<bidirectional_range _Range, typename _Proj = identity,
> diff --git 
> a/libstdc++-v3/testsuite/25_algorithms/stable_partition/constrained.cc 
> b/libstdc++-v3/testsuite/25_algorithms/stable_partition/constrained.cc
> index fc11c6439f9c..4dc267873ae2 100644
> --- a/libstdc++-v3/testsuite/25_algorithms/stable_partition/constrained.cc
> +++ b/libstdc++-v3/testsuite/25_algorithms/stable_partition/constrained.cc
> @@ -21,6 +21,7 @@
>  // { dg-require-effective-target hosted }
>
>  #include <algorithm>
> +#include <ranges>
>  #include <testsuite_hooks.h>
>  #include <testsuite_iterators.h>
>
> @@ -70,9 +71,34 @@ test02()
>      }
>  }
>
> +void
> +test03()
> +{
> +  // PR libstdc++/100795 - ranges::stable_partition should not use
> +  // std::stable_partition directly
> +#if __SIZEOF_INT128__
> +  auto v = std::views::iota(__int128(0), __int128(20));
> +#else
> +  auto v = std::views::iota(0ll, 20ll);
> +#endif
> +
> +  int storage[20] = {2,5,4,3,1,6,7,9,10,8,11,14,12,13,15,16,18,0,19,17};
> +  auto w = v | std::views::transform([&](auto i) -> int& { return 
> storage[i]; });
> +  using type = decltype(w);
> +  using cat = 
> std::iterator_traits<std::ranges::iterator_t<type>>::iterator_category;
> +  static_assert( std::same_as<cat, std::output_iterator_tag> );
> +  static_assert( std::ranges::random_access_range<type> );
> +
> +  auto pred = [] (int a) { return a%2==0; };
> +  ranges::stable_partition(w, pred);
> +  VERIFY( ranges::all_of(w.begin(), w.begin() + 10, pred) );
> +  VERIFY( ranges::none_of(w.begin() + 10, w.end(), pred) );
> +}
> +
>  int
>  main()
>  {
>    test01();
>    test02();
> +  test03();
>  }
> --
> 2.50.0.131.gcf6f63ea6b
>
>
>
> >
> > > +
> > > +   if (__first == __last)
> > > +     return {__first, __first};
> > > +
> > > +   using _DistanceType = iter_difference_t<_Iter>;
> > > +   const _DistanceType __len = ranges::distance(__first, __last);
> > > +
> > > +#if __glibcxx_constexpr_algorithms >= 202306L // >= C++26
> > > +   if consteval {
> > > +     // Simulate a _Temporary_buffer of length 1:
> > > +     iter_value_t<_Iter> __buf = ranges::iter_move(__first);
> > > +     *__first = std::move(__buf);
> > > +     return __detail::__stable_partition_adaptive(__first, __last,
> > > +                                                  __pred_proj,
> > > +                                                  __len, &__buf,
> > > +                                                  _DistanceType(1));
> > > +   }
> > > +#endif
> > > +
> > > +   _Temporary_buffer<_Iter, iter_value_t<_Iter>> __buf(__first,
> > > ptrdiff_t(__len));
> > > +   return __detail::__stable_partition_adaptive(__first, __last,
> > > +                                                __pred_proj,
> > > +                                                __len, __buf.begin(),
> > > +
> > > _DistanceType(__buf.size()));
> > >       }
> > >
> > >     template<bidirectional_range _Range, typename _Proj = identity,
> > > diff --git
> > > a/libstdc++-v3/testsuite/25_algorithms/stable_partition/constrained.cc
> > > b/libstdc++-v3/testsuite/25_algorithms/stable_partition/constrained.cc
> > > index fc11c6439f9c..4dc267873ae2 100644
> > > --- a/libstdc++-v3/testsuite/25_algorithms/stable_partition/constrained.cc
> > > +++ b/libstdc++-v3/testsuite/25_algorithms/stable_partition/constrained.cc
> > > @@ -21,6 +21,7 @@
> > > // { dg-require-effective-target hosted }
> > >
> > > #include <algorithm>
> > > +#include <ranges>
> > > #include <testsuite_hooks.h>
> > > #include <testsuite_iterators.h>
> > >
> > > @@ -70,9 +71,34 @@ test02()
> > >     }
> > > }
> > >
> > > +void
> > > +test03()
> > > +{
> > > +  // PR libstdc++/100795 - ranges::stable_partition should not use
> > > +  // std::stable_partition directly
> > > +#if __SIZEOF_INT128__
> > > +  auto v = std::views::iota(__int128(0), __int128(20));
> > > +#else
> > > +  auto v = std::views::iota(0ll, 20ll);
> > > +#endif
> > > +
> > > +  int storage[20] = {2,5,4,3,1,6,7,9,10,8,11,14,12,13,15,16,18,0,19,17};
> > > +  auto w = v | std::views::transform([&](auto i) -> int& { return
> > > storage[i]; });
> > > +  using type = decltype(w);
> > > +  using cat =
> > > std::iterator_traits<std::ranges::iterator_t<type>>::iterator_category;
> > > +  static_assert( std::same_as<cat, std::output_iterator_tag> );
> > > +  static_assert( std::ranges::random_access_range<type> );
> > > +
> > > +  auto pred = [] (int a) { return a%2==0; };
> > > +  ranges::stable_partition(w, pred);
> > > +  VERIFY( ranges::all_of(w.begin(), w.begin() + 10, pred) );
> > > +  VERIFY( ranges::none_of(w.begin() + 10, w.end(), pred) );
> > > +}
> > > +
> > > int
> > > main()
> > > {
> > >   test01();
> > >   test02();
> > > +  test03();
> > > }
> > > --
> > > 2.50.0.131.gcf6f63ea6b
> > >
> > >
> >
> >
>

Reply via email to