On Fri, Nov 14, 2025 at 3:56 AM Patrick Palka <[email protected]> wrote:

> On Fri, 24 Oct 2025, Tomasz Kamiński wrote:
>
> > This patch changes the implementation of bind_front<f> and bind_back<f>
> to
> > return a _Bind_front_t<_Bind_fn_t<f>, ...> and
> _Bind_back_t<_Bind_fn_t<f>, ...>
> > respectively, replacing the previous lambda-based implementation. The
> prior use
> > of a lambda caused non-conforming behavior with respect to C++23
> [func.require]
> > p8, which requires that bind_front<f>(s), bind_front<f>(move(s)), and
> > bind_front<f>(as_const(s)) produce the same type.
> >
> > Additionally, using specialized structs reduces the size of the
> resulting functor
> > in certain scenarios (see PR).
> >
> > For the zero-argument case, the function now returns a _Bind_fn_t<f>.
> Since this
> > type is already a perfect forwarding call wrapper, it yields the same
> result as
> > _Bind_front_t<_Bind_fn_t<f>>.
>
> Didn't bind_front/back<f> already return a _Bind_fn_t in the zero-arg
> case before this patch?
>
Yes, I mean to say the "still returns":.

>
> >
> > A consequence of this change is that the type returned by
> bind_front<f>(args...)
> > and bind_back<f>(args...) is no longer structural according to GCC.
> However, the
> > standard does not require these resulting functors to be structural, and
> lambda
> > with captures, that were previously returned, are not structural
> according to
> > the standard.
>
> These sentences seem contradictory, the first implies the previous
> implementation returned a structural type, the second states the
> previous implementation returned a non-structural type.
>
I have forgotten about
https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2845,
that reverted
https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2542 for
empty lamdas.
I will just trim it to:
> A consequence of this change is that the types returned by
bind_front<f>(args...)
> and bind_back<f>(args...) are no longer structural. They are also not
required to be structural
> by the standard.


>
> The code changes themselves LGTM.
>
> >
> >       PR libstdc++/122032
> >
> > libstdc++-v3/ChangeLog:
> >
> >       * include/std/functional (std::bind_front<f>, std::bind_back<f>):
> >       Define in terms of _Bind_front_t/_Bind_back_t.
> >       * testsuite/20_util/function_objects/bind_back/nttp.cc: New tests.
> >       * testsuite/20_util/function_objects/bind_front/nttp.cc: New tests.
> > ---
> > not_fn<f>() is not impacted, as it does not have any state entities,
> > and produce stateless lambda.
> >
> > Tested on x86_64-linux. OK for trunk?
> >  libstdc++-v3/include/std/functional           | 50 +++----------------
> >  .../function_objects/bind_back/nttp.cc        | 21 +++++++-
> >  .../function_objects/bind_front/nttp.cc       | 21 +++++++-
> >  3 files changed, 45 insertions(+), 47 deletions(-)
> >
> > diff --git a/libstdc++-v3/include/std/functional
> b/libstdc++-v3/include/std/functional
> > index e4dbb9e0c45..8a6870d71c9 100644
> > --- a/libstdc++-v3/include/std/functional
> > +++ b/libstdc++-v3/include/std/functional
> > @@ -957,7 +957,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >      }
> >
> >  #if __cpp_lib_bind_front >= 202306L
> > -
> >    /** Create call wrapper by partial application of arguments to
> function.
> >     *
> >     * The result of `std::bind_front<fn>(bind_args...)` is a function
> object
> > @@ -970,32 +969,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >    template<auto __fn, typename... _BindArgs>
> >      constexpr decltype(auto)
> >      bind_front(_BindArgs&&... __bind_args)
> > -      noexcept(__and_v<is_nothrow_constructible<_BindArgs>...>)
> > +    noexcept(__and_v<is_nothrow_constructible<_BindArgs>...>)
> >      {
> >        using _Fn = decltype(__fn);
> > -      static_assert(
> > -     (is_constructible_v<decay_t<_BindArgs>, _BindArgs> && ...) &&
> > -     (is_move_constructible_v<decay_t<_BindArgs>> && ...));
> >        if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>)
> >       static_assert(__fn != nullptr);
> >
> >        if constexpr (sizeof...(_BindArgs) == 0)
> >       return _Bind_fn_t<__fn>{};
> > -      else {
> > -     return [... __bound_args(std::forward<_BindArgs>(__bind_args))]
> > -       <typename _Self, typename... _CallArgs>
> > -       (this _Self&&, _CallArgs&&... __call_args)
> > -         noexcept(is_nothrow_invocable_v<
> > -           const _Fn&, __like_t<_Self, decay_t<_BindArgs>>...,
> _CallArgs...>)
> > -         -> decltype(auto)
> > -         requires is_invocable_v<
> > -           const _Fn&, __like_t<_Self, decay_t<_BindArgs>>...,
> _CallArgs...>
> > -       {
> > -         return std::invoke(__fn,
> > -           std::forward_like<_Self>(__bound_args)...,
> > -           std::forward<_CallArgs>(__call_args)...);
> > -       };
> > -      }
> > +      else
> > +        return _Bind_front_t<_Bind_fn_t<__fn>, _BindArgs...>(0,
> > +              _Bind_fn_t<__fn>{},
> std::forward<_BindArgs>(__bind_args)...);
> >      }
> >
> >  #endif // __cpp_lib_bind_front  // C++26
> > @@ -1035,36 +1019,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >    template<auto __fn, typename... _BindArgs>
> >      constexpr decltype(auto)
> >      bind_back(_BindArgs&&... __bind_args)
> > -      noexcept(__and_v<is_nothrow_constructible<_BindArgs>...>)
> > +    noexcept(__and_v<is_nothrow_constructible<_BindArgs>...>)
> >      {
> >        using _Fn = decltype(__fn);
> > -      static_assert(
> > -     (is_constructible_v<decay_t<_BindArgs>, _BindArgs> && ...) &&
> > -     (is_move_constructible_v<decay_t<_BindArgs>> && ...));
> >        if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>)
> >       static_assert(__fn != nullptr);
> >
> >        if constexpr (sizeof...(_BindArgs) == 0)
> >       return _Bind_fn_t<__fn>{};
> >        else
> > -      {
> > -     // Capture arguments in a lambda and return that.
> > -     return [... __bound_args(std::forward<_BindArgs>(__bind_args))]
> > -       <typename _Self, typename... _CallArgs>
> > -       (this _Self&&, _CallArgs&&... __call_args)
> > -         noexcept(is_nothrow_invocable_v<
> > -           const _Fn&, _CallArgs..., __like_t<_Self,
> decay_t<_BindArgs>>...>)
> > -         -> decltype(auto)
> > -         requires is_invocable_v<
> > -           const _Fn&, _CallArgs..., __like_t<_Self,
> decay_t<_BindArgs>>...>
> > -       {
> > -         return std::invoke(__fn,
> > -           std::forward<_CallArgs>(__call_args)...,
> > -           std::forward_like<_Self>(__bound_args)...);
> > -       };
> > -      }
> > +        return _Bind_back_t<_Bind_fn_t<__fn>, _BindArgs...>(0,
> > +              _Bind_fn_t<__fn>{},
> std::forward<_BindArgs>(__bind_args)...);
> >      }
> > -
> >  #endif // __cpp_lib_bind_back  // C++26, nttp
> >  #endif // __cpp_lib_bind_back
> >
> > @@ -1168,7 +1134,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >      }
> >
> >  #if __cpp_lib_not_fn >= 202306L
> > -
> >    /** Wrap a function type to create a function object that negates its
> result.
> >     *
> >     * The function template `std::not_fn` creates a "forwarding call
> wrapper",
> > @@ -1196,7 +1161,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >           !std::invoke(__fn, std::forward<_Args>(__args)...); }
> >       { return !std::invoke(__fn, std::forward<_Args>(__args)...); };
> >      };
> > -
> >  #endif // __cpp_lib_not_fn >= 202306L
> >  #endif // __cpp_lib_not_fn
> >
> > diff --git
> a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
> b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
> > index 3bea8eced43..24bf0466e2d 100644
> > --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
> > +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
> > @@ -24,6 +24,25 @@ test01()
> >    struct F { void operator()(int) {} };
> >    constexpr F f{};
> >
> > +  // Arguments should be decayed:
> > +  static_assert(std::is_same_v<
> > +      decltype(bind_back<f>(std::declval<int>())),
> > +      decltype(bind_back<f>(std::declval<int&>()))
> > +      >);
> > +  static_assert(std::is_same_v<
> > +      decltype(bind_back<f>(std::declval<int>())),
> > +      decltype(bind_back<f>(std::declval<const int&>()))
> > +      >);
> > +
> > +  static_assert(std::is_same_v<
> > +      decltype(bind_back<f>(std::declval<int>(),
> std::declval<float>())),
> > +      decltype(bind_back<f>(std::declval<int&>(),
> std::declval<float&>()))
> > +      >);
> > +  static_assert(std::is_same_v<
> > +      decltype(bind_back<f>(std::declval<int>(),
> std::declval<float>())),
> > +      decltype(bind_back<f>(std::declval<const int&>(),
> std::declval<const float&>()))
> > +      >);
> > +
> >    // Reference wrappers should be handled:
> >    static_assert(!std::is_same_v<
> >        decltype(bind_back<f>(std::declval<int&>())),
> > @@ -270,10 +289,8 @@ test04()
> >    VERIFY(bind_back<g>(1)(2, 3) == 3*1 + 1*2 + 2*3 );
> >    constexpr auto g2 = bind_back<f>(1, 2);
> >    VERIFY(g2(3) == 2*1 + 3*2 + 1*3 );
> > -  VERIFY(bind_back<g1>(2)(3) == 3*1 + 2*2 + 1*3 );
> >    constexpr auto g3 = bind_back<f>(1, 2, 3);
> >    VERIFY(g3() == 1 + 2*2 + 3*3);
> > -  VERIFY(bind_back<g2>(3)() == 1*2 + 2*3 + 3*1 );
> >    return true;
> >  }
> >
> > diff --git
> a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
> b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
> > index 9eb3c432a86..cf84353887b 100644
> > --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
> > +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
> > @@ -24,6 +24,25 @@ test01()
> >    struct F { void operator()(int) {} };
> >    constexpr F f{};
> >
> > +  // Arguments should be decayed:
> > +  static_assert(std::is_same_v<
> > +      decltype(bind_front<f>(std::declval<int>())),
> > +      decltype(bind_front<f>(std::declval<int&>()))
> > +      >);
> > +  static_assert(std::is_same_v<
> > +      decltype(bind_front<f>(std::declval<int>())),
> > +      decltype(bind_front<f>(std::declval<const int&>()))
> > +      >);
> > +
> > +  static_assert(std::is_same_v<
> > +      decltype(bind_front<f>(std::declval<int>(),
> std::declval<float>())),
> > +      decltype(bind_front<f>(std::declval<int&>(),
> std::declval<float&>()))
> > +      >);
> > +  static_assert(std::is_same_v<
> > +      decltype(bind_front<f>(std::declval<int>(),
> std::declval<float>())),
> > +      decltype(bind_front<f>(std::declval<const int&>(),
> std::declval<const float&>()))
> > +      >);
> > +
> >    // Reference wrappers should be handled:
> >    static_assert(!std::is_same_v<
> >        decltype(bind_front<f>(std::declval<int&>())),
> > @@ -269,10 +288,8 @@ test04()
> >    VERIFY( bind_front<g>(1)(2, 3) == 1 + 2*2 + 3*3 );
> >    constexpr auto g2 = bind_front<f>(1, 2);
> >    VERIFY( g2(3) == 1 + 2*2 + 3*3 );
> > -  VERIFY( bind_front<g1>(2)(3) == 1 + 2*2 + 3*3 );
> >    constexpr auto g3 = bind_front<f>(1, 2, 3);
> >    VERIFY( g3() == 1 + 2*2 + 3*3 );
> > -  VERIFY(bind_front<g2>(3)() == 1 + 2*2 + 3*3 );
> >    return true;
> >  }
> >
> > --
> > 2.51.0
> >
> >

Reply via email to