On LWG reflector it was pointed out that returning copy of F, may also impact the result of overload resolution, as without forwarding pointer parameters can be initialized from 0: #include <functional>
struct S { void operator()(long, long); // 1 void operator()(int, void*); // 2 }; void foo() { S{}(0, 0); // calls 2 std::bind_front(S{})(0, 0); // currently calls 1 } https://godbolt.org/z/MPbz8GKb8 I will add the test like above to testsuite (failing test is best kind of documentation), and alternative reduction, by having zero bound args using same specialization for both bind_front and bind_back. On Tue, Aug 26, 2025 at 5:14 PM Jonathan Wakely <jwak...@redhat.com> wrote: > On Wed, 20 Aug 2025 at 11:08, Tomasz Kamiński <tkami...@redhat.com> wrote: > > > > This patch adjusts the implementation of bind_front(f) and bind_back(f) > > (with zero bound arguments), so it returns an auto(f) (a copy of the > > functor), rather than a specialization of Bind_front/Bind_back. This > change > > is mostly unobservable, as standard does not specify return type of > > above functions, but may result in elision of move/copy operations when > > by-value parameters are initialized with prvalues. See the update to > > testMaterialization. > > > > This behavior is technically not allowed by C++23 [func.require] p3, > > which requires that rvalue arguments are delivered as rvalue references, > > forcing their materialization. I have reported a LWG issue to make this > > conforming. > > > > The _Bind_front_t/_Bind_back_t aliases were removed, as the aliased types > > are now spelled only once. > > > > libstdc++-v3/ChangeLog: > > > > * include/std/functional (_Bind_front_t, _Bind_back_t): Remove. > > (std::bind_front, std::bind_back): Return copy of __fn for > > zero bounds args. > > "bounds args" -> "bound args" > > > (_Bind_front, _Bind_back): Remove check for move constructor > > of _Fd, that is now part of bind_xxx functors. > > * testsuite/20_util/function_objects/bind_back/1.cc: Updated > > testMaterialization. > > * testsuite/20_util/function_objects/bind_front/1.cc: Likewise. > > > > Signed-off-by: Tomasz Kamiński <tkami...@redhat.com> > > --- > > This is implemented on top of 20250820081809.50754-1-tkami...@redhat.com > . > > Do we think that this is OK for trunk? As mentioned technically not > > conforming, I do not think user will care about extra move. > > > > The goal here, would be able to say that bind_front<f>(args...) is > > bind_front(nttp<f>, args...). > > The patch seems correct, but I'd like to wait a little while to see > how LWG feel about the proposed change. > > > > > > Tested on x86_64-linux. > > > > libstdc++-v3/include/std/functional | 37 ++++++++++--------- > > .../20_util/function_objects/bind_back/1.cc | 5 ++- > > .../20_util/function_objects/bind_front/1.cc | 5 ++- > > 3 files changed, 26 insertions(+), 21 deletions(-) > > > > diff --git a/libstdc++-v3/include/std/functional > b/libstdc++-v3/include/std/functional > > index b1cda87929d..c8699ac849f 100644 > > --- a/libstdc++-v3/include/std/functional > > +++ b/libstdc++-v3/include/std/functional > > @@ -973,7 +973,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > template<typename _Fd, typename... _BoundArgs> > > struct _Bind_front > > { > > - static_assert(is_move_constructible_v<_Fd>); > > static_assert((is_move_constructible_v<_BoundArgs> && ...)); > > > > // First parameter is to ensure this constructor is never used > > @@ -1081,9 +1080,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > [[no_unique_address]] _BoundArgsStorage _M_bound_args; > > }; > > > > - template<typename _Fn, typename... _Args> > > - using _Bind_front_t = _Bind_front<decay_t<_Fn>, decay_t<_Args>...>; > > - > > /** Create call wrapper by partial application of arguments to > function. > > * > > * The result of `std::bind_front(f, args...)` is a function object > that > > @@ -1094,13 +1090,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > * @since C++20 > > */ > > template<typename _Fn, typename... _Args> > > - constexpr _Bind_front_t<_Fn, _Args...> > > + constexpr auto > > bind_front(_Fn&& __fn, _Args&&... __args) > > - noexcept(is_nothrow_constructible_v<_Bind_front_t<_Fn, _Args...>, > > - int, _Fn, _Args...>) > > + noexcept(__and_<is_nothrow_constructible<decay_t<_Fn>, _Fn>, > > + is_nothrow_constructible<decay_t<_Args>, > _Args>...>::value) > > { > > - return _Bind_front_t<_Fn, _Args...>(0, std::forward<_Fn>(__fn), > > - > std::forward<_Args>(__args)...); > > + using _Fd = decay_t<_Fn>; > > + static_assert(is_move_constructible_v<_Fd>); > > + if constexpr (sizeof...(_Args) == 0) > > + return _Fd(std::forward<_Fn>(__fn)); > > + else > > + return _Bind_front<_Fd, decay_t<_Args>...>(0, > std::forward<_Fn>(__fn), > > + > std::forward<_Args>(__args)...); > > } > > #endif // __cpp_lib_bind_front > > > > @@ -1158,9 +1159,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > [[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 > > @@ -1171,13 +1169,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > * @since C++23 > > */ > > template<typename _Fn, typename... _Args> > > - constexpr _Bind_back_t<_Fn, _Args...> > > + constexpr auto > > bind_back(_Fn&& __fn, _Args&&... __args) > > - noexcept(is_nothrow_constructible_v<_Bind_back_t<_Fn, _Args...>, > > - int, _Fn, _Args...>) > > + noexcept(__and_<is_nothrow_constructible<decay_t<_Fn>, _Fn>, > > + is_nothrow_constructible<decay_t<_Args>, > _Args>...>::value) > > { > > - return _Bind_back_t<_Fn, _Args...>(0, std::forward<_Fn>(__fn), > > - std::forward<_Args>(__args)...); > > + using _Fd = decay_t<_Fn>; > > + static_assert(is_move_constructible_v<_Fd>); > > + if constexpr (sizeof...(_Args) == 0) > > + return _Fd(std::forward<_Fn>(__fn)); > > + else > > + return _Bind_back<_Fd, decay_t<_Args>...>(0, > std::forward<_Fn>(__fn), > > + > std::forward<_Args>(__args)...); > > } > > #endif // __cpp_lib_bind_back > > > > 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..ef7531f0045 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 > > @@ -290,10 +290,11 @@ testMaterialization() > > { return arg.counter; }; > > }; > > > > - // CountedArg is bound to rvalue-reference thus moved > > + // copy of F is returned > > auto f0 = std::bind_back(F{}); > > - VERIFY( f0(CountedArg(), 10) == 1 ); > > + VERIFY( f0(CountedArg(), 10) == 0 ); > > > > + // CountedArg is bound to rvalue-reference thus moved > > auto f1 = std::bind_back(F{}, 10); > > VERIFY( f1(CountedArg()) == 1 ); > > } > > 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..5b0ae3772ab 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 > > @@ -288,10 +288,11 @@ testMaterialization() > > { return arg.counter; }; > > }; > > > > - // CountedArg is bound to rvalue-reference thus moved > > + // copy of F is returned > > auto f0 = std::bind_front(F{}); > > - VERIFY( f0(10, CountedArg()) == 1 ); > > + VERIFY( f0(10, CountedArg()) == 0 ); > > > > + // CountedArg is bound to rvalue-reference thus moved > > auto f1 = std::bind_front(F{}, 10); > > VERIFY( f1(CountedArg()) == 1 ); > > } > > -- > > 2.50.1 > > > >