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

Reply via email to