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. (_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...). 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