On Thu, Sep 4, 2025 at 9:49 AM Tomasz Kaminski <tkami...@redhat.com> wrote:
> > > On Wed, Sep 3, 2025 at 11:20 PM Patrick Palka <ppa...@redhat.com> wrote: > >> On Wed, 3 Sep 2025, Tomasz Kamiński wrote: >> >> > The _Bind_front and _Bind_back class templates are now merged into a >> single >> > _Binder implementation that accepts _Back as a template parameter. This >> makes >> > the bind_back implementation available in C++20 mode, allowing it to be >> used >> > for range adaptor closures. >> > >> > With zero bound arguments, bind_back and bind_front have equivalent >> > functionality. Consequently, _Bind_back_t now produces the same type as >> > bind_front (_Binder<false, _Fd>). A simple copy of the functor cannot be >> > returned in this case, as it would visibly affect overload resolution >> > (see included test cases). >> > >> > libstdc++-v3/ChangeLog: >> > >> > * include/std/functional: (__Bound_arg_storage::_S_apply_front) >> > (__Bound_arg_storage::_S_apply_front): Merged into _S_apply. >> > (__Bound_arg_storage::_S_apply): Merged above, add _Back >> > template parameter. >> > (std::_Bind_front): Renamed to std::_Binder and add _Back >> > template parameter. >> > (std::_Binder): Renamed from std::_Bind_front. >> > (_Binder::_Result_t, _Binder::_S_noexcept_invoke): Define. >> > (_Binder::operator()): Use _Result_t and _S_noexcept_invoke. >> > (_Binder::_S_call): Handle zero args specially. >> > (std::_Bind_front_t, std::_Bind_back_t): Defined in terms >> > of _Binder. >> > (std::_Bind_back): Merged into _Binder. >> > * testsuite/20_util/function_objects/bind_back/1.cc: New tests. >> > * testsuite/20_util/function_objects/bind_back/111327.cc: Updated >> > error messages. >> > * testsuite/20_util/function_objects/bind_front/1.cc: New tests. >> > * testsuite/20_util/function_objects/bind_front/111327.cc: Updated >> > error messages. >> > --- >> > Tested on x86_64-linux locally. OK for trunk? >> > >> > libstdc++-v3/include/std/functional | 172 +++++++----------- >> > .../20_util/function_objects/bind_back/1.cc | 16 +- >> > .../function_objects/bind_back/111327.cc | 3 +- >> > .../20_util/function_objects/bind_front/1.cc | 15 ++ >> > .../function_objects/bind_front/111327.cc | 2 +- >> > 5 files changed, 103 insertions(+), 105 deletions(-) >> > >> > diff --git a/libstdc++-v3/include/std/functional >> b/libstdc++-v3/include/std/functional >> > index b1cda87929d..4122e8f9988 100644 >> > --- a/libstdc++-v3/include/std/functional >> > +++ b/libstdc++-v3/include/std/functional >> > @@ -921,9 +921,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> > >> std::forward<_BoundArgs>(__args)...); >> > } >> > >> > -#ifdef __cpp_lib_bind_front // C++ >= 20 >> > +#if __cplusplus >= 202002L >> > template<size_t, typename _Tp> >> > - struct _Indexed_bound_arg >> > + struct _Indexed_bound_arg >> >> Unintended whitespace change? >> > Intended whitespace change. Our formatting policy says that we should > indent after > template, and I have noticed I have forgotten to do that. > >> >> > { >> > [[no_unique_address]] _Tp _M_val; >> > }; >> > @@ -931,24 +931,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> > template<typename... _IndexedArgs> >> > struct _Bound_arg_storage : _IndexedArgs... >> > { >> > - template<typename _Fd, typename _Self, typename... _CallArgs> >> > + template<bool _Back, typename _Fd, typename _Self, typename... >> _CallArgs> >> > static constexpr >> > decltype(auto) >> > - _S_apply_front(_Fd&& __fd, _Self&& __self, _CallArgs&&... >> __call_args) >> > + _S_apply(_Fd&& __fd, _Self&& __self, _CallArgs&&... __call_args) >> > { >> > - return std::invoke(std::forward<_Fd>(__fd), >> > - __like_t<_Self, >> _IndexedArgs>(__self)._M_val..., >> > - std::forward<_CallArgs>(__call_args)...); >> >> It occurs to me that we should use std::__invoke throughout, since >> std::invoke is just a simple wrapper for it. >> > I have done this in patch v2. I could move it to this patch. > I will move the change to this patch, so the v2 is a clear move, without any sneaky changes. > >> LGTM besides that (which could be done in a separate patch). >> >> > - } >> > - >> > - template<typename _Fd, typename _Self, typename... _CallArgs> >> > - static constexpr >> > - decltype(auto) >> > - _S_apply_back(_Fd&& __fd, _Self&& __self, _CallArgs&&... >> __call_args) >> > - { >> > - return std::invoke(std::forward<_Fd>(__fd), >> > - std::forward<_CallArgs>(__call_args)..., >> > - __like_t<_Self, >> _IndexedArgs>(__self)._M_val...); >> > + if constexpr (_Back) >> > + return std::invoke(std::forward<_Fd>(__fd), >> > + std::forward<_CallArgs>(__call_args)..., >> > + __like_t<_Self, >> _IndexedArgs>(__self)._M_val...); >> > + else >> > + return std::invoke(std::forward<_Fd>(__fd), >> > + __like_t<_Self, >> _IndexedArgs>(__self)._M_val..., >> > + std::forward<_CallArgs>(__call_args)...); >> > } >> > }; >> > >> > @@ -970,9 +965,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> > } >> > } >> > >> > - template<typename _Fd, typename... _BoundArgs> >> > - struct _Bind_front >> > + template<bool _Back, typename _Fd, typename... _BoundArgs> >> > + class _Binder >> > { >> > + template<typename _Self, typename... _CallArgs> >> > + using _Result_t = __conditional_t< >> > + _Back, >> > + invoke_result<__like_t<_Self, _Fd>, >> > + _CallArgs..., __like_t<_Self, _BoundArgs>...>, >> > + invoke_result<__like_t<_Self, _Fd>, >> > + __like_t<_Self, _BoundArgs>..., >> _CallArgs...>>::type; >> > + >> > + template<typename _Self, typename... _CallArgs> >> > + static consteval bool >> > + _S_noexcept_invocable() >> > + { >> > + if constexpr (_Back) >> > + return is_nothrow_invocable_v< __like_t<_Self, _Fd>, >> > + _CallArgs..., __like_t<_Self, _BoundArgs>...>; >> > + else >> > + return is_nothrow_invocable_v<__like_t<_Self, _Fd>, >> > + __like_t<_Self, _BoundArgs>..., _CallArgs...>; >> > + } >> > + >> > + public: >> > static_assert(is_move_constructible_v<_Fd>); >> > static_assert((is_move_constructible_v<_BoundArgs> && ...)); >> > >> > @@ -980,7 +996,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> > // instead of the copy/move constructor. >> > template<typename _Fn, typename... _Args> >> > explicit constexpr >> > - _Bind_front(int, _Fn&& __fn, _Args&&... __args) >> > + _Binder(int, _Fn&& __fn, _Args&&... __args) >> > noexcept(__and_<is_nothrow_constructible<_Fd, _Fn>, >> > is_nothrow_constructible<_BoundArgs, >> _Args>...>::value) >> > : _M_fd(std::forward<_Fn>(__fn)), >> > @@ -989,43 +1005,37 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> > >> > #if __cpp_explicit_this_parameter >> > template<typename _Self, typename... _CallArgs> >> > - constexpr >> > - invoke_result_t<__like_t<_Self, _Fd>, __like_t<_Self, >> _BoundArgs>..., _CallArgs...> >> > + constexpr _Result_t<_Self, _CallArgs...> >> > operator()(this _Self&& __self, _CallArgs&&... __call_args) >> > - noexcept(is_nothrow_invocable_v<__like_t<_Self, _Fd>, >> > - __like_t<_Self, _BoundArgs>..., >> _CallArgs...>) >> > + noexcept(_S_noexcept_invocable<_Self, _CallArgs...>()) >> > { >> > - return _S_call(__like_t<_Self, _Bind_front>(__self), >> > + return _S_call(__like_t<_Self, _Binder>(__self), >> > std::forward<_CallArgs>(__call_args)...); >> > } >> > #else >> > template<typename... _CallArgs> >> > requires true >> > - constexpr >> > - invoke_result_t<_Fd&, _BoundArgs&..., _CallArgs...> >> > + constexpr _Result_t<_Binder&, _CallArgs...> >> > operator()(_CallArgs&&... __call_args) & >> > - noexcept(is_nothrow_invocable_v<_Fd&, _BoundArgs&..., >> _CallArgs...>) >> > + noexcept(_S_noexcept_invocable<_Binder&, _CallArgs...>()) >> > { >> > return _S_call(*this, std::forward<_CallArgs>(__call_args)...); >> > } >> > >> > template<typename... _CallArgs> >> > requires true >> > - constexpr >> > - invoke_result_t<const _Fd&, const _BoundArgs&..., _CallArgs...> >> > + constexpr _Result_t<const _Binder&, _CallArgs...> >> > operator()(_CallArgs&&... __call_args) const & >> > - noexcept(is_nothrow_invocable_v<const _Fd&, const _BoundArgs&..., >> > - _CallArgs...>) >> > + noexcept(_S_noexcept_invocable<const _Binder&, _CallArgs...>()) >> > { >> > return _S_call(*this, std::forward<_CallArgs>(__call_args)...); >> > } >> > >> > template<typename... _CallArgs> >> > requires true >> > - constexpr >> > - invoke_result_t<_Fd, _BoundArgs..., _CallArgs...> >> > + constexpr _Result_t<_Binder&&, _CallArgs...> >> > operator()(_CallArgs&&... __call_args) && >> > - noexcept(is_nothrow_invocable_v<_Fd, _BoundArgs..., _CallArgs...>) >> > + noexcept(_S_noexcept_invocable<_Binder&&, _CallArgs...>()) >> > { >> > return _S_call(std::move(*this), >> > std::forward<_CallArgs>(__call_args)...); >> > @@ -1033,11 +1043,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> > >> > template<typename... _CallArgs> >> > requires true >> > - constexpr >> > - invoke_result_t<const _Fd, const _BoundArgs..., _CallArgs...> >> > + constexpr _Result_t<const _Binder&&, _CallArgs...> >> > operator()(_CallArgs&&... __call_args) const && >> > - noexcept(is_nothrow_invocable_v<const _Fd, const _BoundArgs..., >> > - _CallArgs...>) >> > + noexcept(_S_noexcept_invocable<const _Binder&&, _CallArgs...>()) >> > { >> > return _S_call(std::move(*this), >> > std::forward<_CallArgs>(__call_args)...); >> > @@ -1066,15 +1074,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> > decltype(auto) >> > _S_call(_Tp&& __g, _CallArgs&&... __call_args) >> > { >> > - if constexpr (sizeof...(_BoundArgs) == 1) >> > - return std::invoke(std::forward<_Tp>(__g)._M_fd, >> > - std::forward<_Tp>(__g)._M_bound_args, >> > - std::forward<_CallArgs>(__call_args)...); >> > - else >> > - return _BoundArgsStorage::_S_apply_front( >> > + if constexpr (sizeof...(_BoundArgs) > 1) >> > + return _BoundArgsStorage::template _S_apply<_Back>( >> > std::forward<_Tp>(__g)._M_fd, >> > std::forward<_Tp>(__g)._M_bound_args, >> > std::forward<_CallArgs>(__call_args)...); >> > + else if constexpr (sizeof...(_BoundArgs) == 0) >> > + return std::invoke(std::forward<_Tp>(__g)._M_fd, >> > + std::forward<_CallArgs>(__call_args)...); >> > + else if constexpr (_Back) // sizeof...(_BoundArgs) == 1 >> > + return std::invoke(std::forward<_Tp>(__g)._M_fd, >> > + std::forward<_CallArgs>(__call_args)..., >> > + std::forward<_Tp>(__g)._M_bound_args); >> > + else // !_Back && sizeof...(_BoundArgs) == 1 >> > + return std::invoke(std::forward<_Tp>(__g)._M_fd, >> > + std::forward<_Tp>(__g)._M_bound_args, >> > + std::forward<_CallArgs>(__call_args)...); >> > + >> > } >> > >> > [[no_unique_address]] _Fd _M_fd; >> > @@ -1082,8 +1098,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> > }; >> > >> > template<typename _Fn, typename... _Args> >> > - using _Bind_front_t = _Bind_front<decay_t<_Fn>, decay_t<_Args>...>; >> > + using _Bind_front_t = _Binder<false, decay_t<_Fn>, >> decay_t<_Args>...>; >> > + >> > + // for zero bounds args behavior of bind_front and bind_back is the >> same, >> > + // so reuse _Bind_front_t, i.e. _Binder<false, ...> >> > + template<typename _Fn, typename... _Args> >> > + using _Bind_back_t >> > + = _Binder<(sizeof...(_Args) > 0), decay_t<_Fn>, >> decay_t<_Args>...>; >> > +#endif // __cplusplus >= 202002L >> > >> > +#ifdef __cpp_lib_bind_front // C++ >= 20 >> > /** Create call wrapper by partial application of arguments to >> function. >> > * >> > * The result of `std::bind_front(f, args...)` is a function object >> that >> > @@ -1105,62 +1129,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> > #endif // __cpp_lib_bind_front >> > >> > #ifdef __cpp_lib_bind_back // C++ >= 23 >> > - template<typename _Fd, typename... _BoundArgs> >> > - struct _Bind_back >> > - { >> > - static_assert(is_move_constructible_v<_Fd>); >> > - static_assert((is_move_constructible_v<_BoundArgs> && ...)); >> > - >> > - // First parameter is to ensure this constructor is never used >> > - // instead of the copy/move constructor. >> > - template<typename _Fn, typename... _Args> >> > - explicit constexpr >> > - _Bind_back(int, _Fn&& __fn, _Args&&... __args) >> > - noexcept(__and_<is_nothrow_constructible<_Fd, _Fn>, >> > - is_nothrow_constructible<_BoundArgs, >> _Args>...>::value) >> > - : _M_fd(std::forward<_Fn>(__fn)), >> > - >> >> _M_bound_args(__make_bound_args<_BoundArgs...>(std::forward<_Args>(__args)...)) >> > - { static_assert(sizeof...(_Args) == sizeof...(_BoundArgs)); } >> > - >> > - template<typename _Self, typename... _CallArgs> >> > - constexpr >> > - invoke_result_t<__like_t<_Self, _Fd>, _CallArgs..., >> __like_t<_Self, _BoundArgs>...> >> > - operator()(this _Self&& __self, _CallArgs&&... __call_args) >> > - noexcept(is_nothrow_invocable_v<__like_t<_Self, _Fd>, >> > - _CallArgs..., __like_t<_Self, >> _BoundArgs>...>) >> > - { >> > - return _S_call(__like_t<_Self, _Bind_back>(__self), >> > - std::forward<_CallArgs>(__call_args)...); >> > - } >> > - >> > - private: >> > - using _BoundArgsStorage >> > - // _BoundArgs are required to be move-constructible, so this is >> valid. >> > - = >> decltype(__make_bound_args<_BoundArgs...>(std::declval<_BoundArgs>()...)); >> > - >> > - template<typename _Tp, typename... _CallArgs> >> > - static constexpr >> > - decltype(auto) >> > - _S_call(_Tp&& __g, _CallArgs&&... __call_args) >> > - { >> > - if constexpr (sizeof...(_BoundArgs) == 1) >> > - return std::invoke(std::forward<_Tp>(__g)._M_fd, >> > - std::forward<_CallArgs>(__call_args)..., >> > - std::forward<_Tp>(__g)._M_bound_args); >> > - else >> > - return _BoundArgsStorage::_S_apply_back( >> > - std::forward<_Tp>(__g)._M_fd, >> > - std::forward<_Tp>(__g)._M_bound_args, >> > - std::forward<_CallArgs>(__call_args)...); >> > - } >> > - >> > - [[no_unique_address]] _Fd _M_fd; >> > - [[no_unique_address]] _BoundArgsStorage _M_bound_args; >> > - }; >> > - >> > - template<typename _Fn, typename... _Args> >> > - using _Bind_back_t = _Bind_back<decay_t<_Fn>, decay_t<_Args>...>; >> > - >> > /** Create call wrapper by partial application of arguments to >> function. >> > * >> > * The result of `std::bind_back(f, args...)` is a function object >> that >> > diff --git >> a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc >> b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc >> > index a31528fc755..7141be282c0 100644 >> > --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc >> > +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc >> > @@ -57,7 +57,6 @@ test01() >> > decltype(bind_back(std::declval<const F&>(), std::declval<const >> int&>(), std::declval<const float&>())) >> > >); >> > >> > - >> > // Reference wrappers should be handled: >> > static_assert(!std::is_same_v< >> > decltype(bind_back(std::declval<F>(), std::declval<int&>())), >> > @@ -197,6 +196,21 @@ testCallArgs(Args... args) >> > VERIFY( q.as_const && q.as_lvalue ); >> > q = cg(std::move(ci)); >> > VERIFY( q.as_const && ! q.as_lvalue ); >> > + >> > + struct S >> > + { >> > + int operator()(long, long, Args...) const { return 1; } >> > + int operator()(int, void*, Args...) const { return 2; } >> > + }; >> > + >> > + S s; >> > + // literal zero can be converted to any pointer, so (int, void*) >> > + // is best candidate >> > + VERIFY( s(0, 0, args...) == 2 ); >> > + // both arguments are bound to int&&, and no longer can be >> > + // converted to pointer, (long, long) is only candidate >> > + VERIFY( bind_back(s)(0, 0, args...) == 1 ); >> > + VERIFY( bind_back(s, args...)(0, 0) == 1 ); >> > } >> > >> > void >> > diff --git >> a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc >> b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc >> > index de3ae47e37f..8e7dacf80df 100644 >> > --- >> a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc >> > +++ >> b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc >> > @@ -50,4 +50,5 @@ int main() { >> > std::move(std::as_const(g2))(); >> > } >> > >> > -// { dg-error "no type named 'type' in 'struct std::invoke_result" "" >> { target c++23 } 0 } >> > +// { dg-error "no type named 'type' in 'std::__conditional_t<false, >> std::invoke_result<" "" { target c++23 } 0 } >> > +// { dg-error "no type named 'type' in 'std::__conditional_t<true, >> std::invoke_result<" "" { target c++23 } 0 } >> > diff --git >> a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc >> b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc >> > index ef28de8321b..93efb2efea4 100644 >> > --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc >> > +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc >> > @@ -196,6 +196,21 @@ testCallArgs(Args... args) >> > VERIFY( q.as_const && q.as_lvalue ); >> > q = cg(std::move(ci)); >> > VERIFY( q.as_const && ! q.as_lvalue ); >> > + >> > + struct S >> > + { >> > + int operator()(Args..., long, long) const { return 1; } >> > + int operator()(Args..., int, void*) const { return 2; } >> > + }; >> > + >> > + S s; >> > + // literal zero can be converted to any pointer, so (int, void*) >> > + // is best candidate >> > + VERIFY( s(args..., 0, 0) == 2 ); >> > + // both arguments are bound to int&&, and no longer can be >> > + // converted to pointer, (long, long) is only candidate >> > + VERIFY( bind_front(s)(args..., 0, 0) == 1 ); >> > + VERIFY( bind_front(s, args...)(0, 0) == 1 ); >> > } >> > >> > void >> > diff --git >> a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc >> b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc >> > index 6694322d67e..58832a61a7e 100644 >> > --- >> a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc >> > +++ >> b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc >> > @@ -50,4 +50,4 @@ int main() { >> > std::move(std::as_const(g2))(); >> > } >> > >> > -// { dg-error "no type named 'type' in 'struct std::invoke_result" "" >> { target c++23 } 0 } >> > +// { dg-error "no type named 'type' in 'std::__conditional_t<false, >> std::invoke_result<" "" { target c++23 } 0 } >> > -- >> > 2.51.0 >> > >> > > >