On Wed, Sep 3, 2025 at 11:20 PM Patrick Palka <[email protected]> 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.
>
> 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
> >
> >