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