On Thu, 12 Jun 2025, Jonathan Wakely wrote: > > > On Thu, 12 Jun 2025, 16:56 Patrick Palka, <ppa...@redhat.com> wrote: > Tested on x86_64-pc-linux-gnu, does this look OK for trunk? > > I'm sure if introducing a new overload is preferable to > introducing a constexpr if branch in the existing overload. > > > I think a constexpr if branch in the existing functions is cheaper. We need > to check the traits either way, but we can avoid doing overload resolution.
Ack. I opted to just define specialized functors for these helpers instead of using lambdas. That way we can easily optimize the case where only one of the functions is stateless. Changes in v2: - Use a functor utilizing [[no_unique_address]] instead of a lambda, to also concisely handle the case where only one of the functino object is stateless. In turn check is_copy_xible instead of is_default_xible. -- >8 -- When creating a composite comparator/predicate that invokes a given projection function, we don't need to capture a stateless function object by reference, instead we can capture it by value and use [[no_unique_address]] to elide its storage. This makes using __make_comp_proj zero-cost in the common case where the comparator or projection (or both) are stateless. libstdc++-v3/ChangeLog: * include/bits/ranges_algo.h (__detail::_Comp_proj): New. (__detail::__make_comp_proj): Use it instead. (__detail::_Pred_proj): New. (__detail::__make_pred_proj): Use it instead. --- libstdc++-v3/include/bits/ranges_algo.h | 70 +++++++++++++++++++------ 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/libstdc++-v3/include/bits/ranges_algo.h b/libstdc++-v3/include/bits/ranges_algo.h index 9f94c6b1d57e..ce48ca047b69 100644 --- a/libstdc++-v3/include/bits/ranges_algo.h +++ b/libstdc++-v3/include/bits/ranges_algo.h @@ -49,27 +49,65 @@ namespace ranges namespace __detail { template<typename _Comp, typename _Proj> - constexpr auto + struct _Comp_proj + { + [[no_unique_address]] + __conditional_t<is_empty_v<_Comp> && is_copy_constructible_v<_Comp>, + _Comp, _Comp&> _M_comp; + + [[no_unique_address]] + __conditional_t<is_empty_v<_Proj> && is_copy_constructible_v<_Proj>, + _Proj, _Proj&> _M_proj; + + constexpr + _Comp_proj(_Comp& __comp, _Proj& __proj) + : _M_comp(__comp), _M_proj(__proj) + { } + + template<typename _Tp, typename _Up> + constexpr bool + operator()(_Tp&& __x, _Up&& __y) + { + return std::__invoke(_M_comp, + std::__invoke(_M_proj, std::forward<_Tp>(__x)), + std::__invoke(_M_proj, std::forward<_Up>(__y))); + } + }; + + template<typename _Comp, typename _Proj> + constexpr _Comp_proj<_Comp, _Proj> __make_comp_proj(_Comp& __comp, _Proj& __proj) + { return {__comp, __proj}; } + + template<typename _Pred, typename _Proj> + struct _Pred_proj { - return [&] (auto&& __lhs, auto&& __rhs) -> bool { - using _TL = decltype(__lhs); - using _TR = decltype(__rhs); - return std::__invoke(__comp, - std::__invoke(__proj, std::forward<_TL>(__lhs)), - std::__invoke(__proj, std::forward<_TR>(__rhs))); - }; - } + [[no_unique_address]] + __conditional_t<is_empty_v<_Pred> && is_copy_constructible_v<_Pred>, + _Pred, _Pred&> _M_pred; + + [[no_unique_address]] + __conditional_t<is_empty_v<_Proj> && is_copy_constructible_v<_Proj>, + _Proj, _Proj&> _M_proj; + + constexpr + _Pred_proj(_Pred& __pred, _Proj& __proj) + : _M_pred(__pred), _M_proj(__proj) + { } + + template<typename _Tp> + constexpr bool + operator()(_Tp&& __x) + { + return std::__invoke(_M_pred, + std::__invoke(_M_proj, std::forward<_Tp>(__x))); + } + }; template<typename _Pred, typename _Proj> - constexpr auto + constexpr _Pred_proj<_Pred, _Proj> __make_pred_proj(_Pred& __pred, _Proj& __proj) - { - return [&] <typename _Tp> (_Tp&& __arg) -> bool { - return std::__invoke(__pred, - std::__invoke(__proj, std::forward<_Tp>(__arg))); - }; - } + { return {__pred, __proj}; } } // namespace __detail struct __all_of_fn -- 2.50.0.rc2 > > > > -- >8 -- > > When composing a comparator/predicate and projection function, if both > are stateless (and default constructible) then we don't need to capture > them by reference; we can just return a captureless lambda and > materialize > them on the spot. > > libstdc++-v3/ChangeLog: > > * include/bits/ranges_algo.h (__detail::__make_comp_proj): New > more specialized overload returning a stateless lambda if both > the comparator/predicate and projection are stateless. > (__detail::__make_pred_proj): Likewise. > --- > libstdc++-v3/include/bits/ranges_algo.h | 27 +++++++++++++++++++++++++ > 1 file changed, 27 insertions(+) > > diff --git a/libstdc++-v3/include/bits/ranges_algo.h > b/libstdc++-v3/include/bits/ranges_algo.h > index 819316fa79c3..7dc84d639fbd 100644 > --- a/libstdc++-v3/include/bits/ranges_algo.h > +++ b/libstdc++-v3/include/bits/ranges_algo.h > @@ -61,6 +61,21 @@ namespace ranges > }; > } > > + template<typename _Comp, typename _Proj> > + requires is_empty_v<_Comp> && is_default_constructible_v<_Comp> > + && is_empty_v<_Proj> && is_default_constructible_v<_Proj> > + constexpr auto > + __make_comp_proj(_Comp& __comp, _Proj& __proj) > + { > + return [] (auto&& __lhs, auto&& __rhs) -> bool { > + using _TL = decltype(__lhs); > + using _TR = decltype(__rhs); > + return std::__invoke(_Comp{}, > + std::__invoke(_Proj{}, > std::forward<_TL>(__lhs)), > + std::__invoke(_Proj{}, > std::forward<_TR>(__rhs))); > + }; > + } > + > template<typename _Pred, typename _Proj> > constexpr auto > __make_pred_proj(_Pred& __pred, _Proj& __proj) > @@ -70,6 +85,18 @@ namespace ranges > std::__invoke(__proj, > std::forward<_Tp>(__arg))); > }; > } > + > + template<typename _Pred, typename _Proj> > + requires is_empty_v<_Pred> && is_default_constructible_v<_Pred> > + && is_empty_v<_Proj> && is_default_constructible_v<_Pred> > + constexpr auto > + __make_pred_proj(_Pred& __pred, _Proj& __proj) > + { > + return [] <typename _Tp> (_Tp&& __arg) -> bool { > + return std::__invoke(_Pred{}, > + std::__invoke(_Proj{}, > std::forward<_Tp>(__arg))); > + }; > + } > } // namespace __detail > > struct __all_of_fn > -- > 2.50.0.rc2 > > >