[PATCH] libstdc++: Optimized bind_front/bind_back for zero bound args.
Tomasz Kaminski
tkaminsk@redhat.com
Wed Aug 27 06:39:10 GMT 2025
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 <jwakely@redhat.com> wrote:
> On Wed, 20 Aug 2025 at 11:08, Tomasz Kamiński <tkaminsk@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 <tkaminsk@redhat.com>
> > ---
> > This is implemented on top of 20250820081809.50754-1-tkaminsk@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
> >
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20250827/5d7654bd/attachment-0001.htm>
More information about the Libstdc++
mailing list