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
>> >
>> >
>
>

Reply via email to