On Fri, 12 Sept 2025 at 18:39, Patrick Palka <[email protected]> wrote:
>
> Tested on x86_64-pc-linux-gnu, does this look OK for trunk?
> Patch generated with -w due to otherwise noisy whitespace changes.
>
> -- >8 --
>
> The ranges::shuffle optimization, copied from std::shuffle, has a
> two-at-a-time PRNG optimization that considers the range of the
> PRNG vs the size of the range. But in C++20 a sentinel is not
> necessarily sized so we can't unconditionally do __last - __first.
>
> We could instead use ranges::size, but that'd take linear time for a
> non-sized sentinel which makes the optimization less clear of a win.
> So this patch instead makes us only consider this optimization for
> arguments that model sized_sentinel_for or sized_range.
>
> PR libstdc++/121917
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/ranges_algo.h (__shuffle_fn::_S_impl): Factor
> out from main operator() overload. Add optional size parameter,
> and only consider the two-at-a-time PRNG optimization if the
> size is given.
> (__shuffle_fn::operator()): Adjust to call _S_impl directly,
> computing the range size for sized_sentinel_for or sized_range
> arguments.
> * testsuite/25_algorithms/shuffle/constrained.cc:
> ---
> libstdc++-v3/include/bits/ranges_algo.h | 35 ++++++++++++++-----
> .../25_algorithms/shuffle/constrained.cc | 18 ++++++++++
> 2 files changed, 44 insertions(+), 9 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/ranges_algo.h
> b/libstdc++-v3/include/bits/ranges_algo.h
> index 6e1e06cb2d0f..855ab1395149 100644
> --- a/libstdc++-v3/include/bits/ranges_algo.h
> +++ b/libstdc++-v3/include/bits/ranges_algo.h
> @@ -1945,12 +1945,10 @@ namespace ranges
>
> struct __shuffle_fn
> {
> - template<random_access_iterator _Iter, sentinel_for<_Iter> _Sent,
> - typename _Gen>
> - requires permutable<_Iter>
> - && uniform_random_bit_generator<remove_reference_t<_Gen>>
> - _Iter
> - operator()(_Iter __first, _Sent __last, _Gen&& __g) const
> + template<typename _Iter, typename _Sent, typename _Gen>
> + static _Iter
> + _S_impl(_Iter __first, _Sent __last, _Gen&& __g,
> + iter_difference_t<_Iter> __n)
This _S_impl should be private, however ...
> {
> // FIXME: Correctly handle integer-class difference types.
> if (__first == __last)
> @@ -1964,8 +1962,10 @@ namespace ranges
> using __uc_type
> = common_type_t<typename remove_reference_t<_Gen>::result_type,
> __ud_type>;
>
> + if (__n != -1)
Having this as a runtime check for != 1 seems a little inelegant when
we've already determined statically whether we want to consider the
optimization.
We could leave the main implementation in the operator()(Iter, Sent,
Gen&&) overload and just add:
if constexpr (sized_sentinel_for<Sent, Iter>)
here instead of 'if ( n != 1)'
Then in the operator()(R&&, Gen&&) overload do:
if constexpr (sized_range<_Range>)
{
auto __first = ranges::begin(__r);
return (*this)(__first, __first + ranges::size(__r),
std::forward<_Gen>(__g));
}
else
return (*this)(ranges::begin(__r), ranges::end(__r),
std::forward<_Gen>(__g));
This way we don't need to pass n to _S_impl, we just ensure that we
pass a sized sentinel instead, and that enables the optimization.