The _Bind_front and _Bind_back class templates are now merged into a single _Binder implementation that accepts _Back as a template parameter. This makes the bind_back implementation available in C++20 mode, allowing it to be used for range adaptor closures.
With zero bound arguments, bind_back and bind_front have equivalent functionality. Consequently, _Bind_back_t now produces the same type as bind_front (_Binder<false, _Fd>). A simple copy of the functor cannot be returned in this case, as it would visibly affect overload resolution (see included test cases). libstdc++-v3/ChangeLog: * include/std/functional: (__Bound_arg_storage::_S_apply_front) (__Bound_arg_storage::_S_apply_front): Merged into _S_apply. (__Bound_arg_storage::_S_apply): Merged above, add _Back template parameter. (std::_Bind_front): Renamed to std::_Binder and add _Back template parameter. (std::_Binder): Renamed from std::_Bind_front. (_Binder::_Result_t, _Binder::_S_noexcept_invoke): Define. (_Binder::operator()): Use _Result_t and _S_noexcept_invoke. (_Binder::_S_call): Handle zero args specially. (std::_Bind_front_t, std::_Bind_back_t): Defined in terms of _Binder. (std::_Bind_back): Merged into _Binder. * testsuite/20_util/function_objects/bind_back/1.cc: New tests. * testsuite/20_util/function_objects/bind_front/1.cc: New tests. --- Returning a copy of the functor for zero bound args, turned to not really be viable approach, as it changes overload resolution. This makes bind_front(f) and bind_back(f) to reuse same functor, and make _Bind_back_t available in C++20, in preparation for _Partial rework. Tested on x86_64-linux locally. OK for trunk? libstdc++-v3/include/std/functional | 172 +++++++----------- .../20_util/function_objects/bind_back/1.cc | 16 +- .../function_objects/bind_back/111327.cc | 3 +- .../20_util/function_objects/bind_front/1.cc | 15 ++ .../function_objects/bind_front/111327.cc | 2 +- 5 files changed, 103 insertions(+), 105 deletions(-) diff --git a/libstdc++-v3/include/std/functional b/libstdc++-v3/include/std/functional index b1cda87929d..4122e8f9988 100644 --- a/libstdc++-v3/include/std/functional +++ b/libstdc++-v3/include/std/functional @@ -921,9 +921,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION std::forward<_BoundArgs>(__args)...); } -#ifdef __cpp_lib_bind_front // C++ >= 20 +#if __cplusplus >= 202002L template<size_t, typename _Tp> - struct _Indexed_bound_arg + struct _Indexed_bound_arg { [[no_unique_address]] _Tp _M_val; }; @@ -931,24 +931,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template<typename... _IndexedArgs> struct _Bound_arg_storage : _IndexedArgs... { - template<typename _Fd, typename _Self, typename... _CallArgs> + template<bool _Back, typename _Fd, typename _Self, typename... _CallArgs> static constexpr decltype(auto) - _S_apply_front(_Fd&& __fd, _Self&& __self, _CallArgs&&... __call_args) + _S_apply(_Fd&& __fd, _Self&& __self, _CallArgs&&... __call_args) { - return std::invoke(std::forward<_Fd>(__fd), - __like_t<_Self, _IndexedArgs>(__self)._M_val..., - std::forward<_CallArgs>(__call_args)...); - } - - template<typename _Fd, typename _Self, typename... _CallArgs> - static constexpr - decltype(auto) - _S_apply_back(_Fd&& __fd, _Self&& __self, _CallArgs&&... __call_args) - { - return std::invoke(std::forward<_Fd>(__fd), - std::forward<_CallArgs>(__call_args)..., - __like_t<_Self, _IndexedArgs>(__self)._M_val...); + if constexpr (_Back) + return std::invoke(std::forward<_Fd>(__fd), + std::forward<_CallArgs>(__call_args)..., + __like_t<_Self, _IndexedArgs>(__self)._M_val...); + else + return std::invoke(std::forward<_Fd>(__fd), + __like_t<_Self, _IndexedArgs>(__self)._M_val..., + std::forward<_CallArgs>(__call_args)...); } }; @@ -970,9 +965,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } } - template<typename _Fd, typename... _BoundArgs> - struct _Bind_front + template<bool _Back, typename _Fd, typename... _BoundArgs> + class _Binder { + template<typename _Self, typename... _CallArgs> + using _Result_t = __conditional_t< + _Back, + invoke_result<__like_t<_Self, _Fd>, + _CallArgs..., __like_t<_Self, _BoundArgs>...>, + invoke_result<__like_t<_Self, _Fd>, + __like_t<_Self, _BoundArgs>..., _CallArgs...>>::type; + + template<typename _Self, typename... _CallArgs> + static consteval bool + _S_noexcept_invocable() + { + if constexpr (_Back) + return is_nothrow_invocable_v< __like_t<_Self, _Fd>, + _CallArgs..., __like_t<_Self, _BoundArgs>...>; + else + return is_nothrow_invocable_v<__like_t<_Self, _Fd>, + __like_t<_Self, _BoundArgs>..., _CallArgs...>; + } + + public: static_assert(is_move_constructible_v<_Fd>); static_assert((is_move_constructible_v<_BoundArgs> && ...)); @@ -980,7 +996,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // instead of the copy/move constructor. template<typename _Fn, typename... _Args> explicit constexpr - _Bind_front(int, _Fn&& __fn, _Args&&... __args) + _Binder(int, _Fn&& __fn, _Args&&... __args) noexcept(__and_<is_nothrow_constructible<_Fd, _Fn>, is_nothrow_constructible<_BoundArgs, _Args>...>::value) : _M_fd(std::forward<_Fn>(__fn)), @@ -989,43 +1005,37 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #if __cpp_explicit_this_parameter template<typename _Self, typename... _CallArgs> - constexpr - invoke_result_t<__like_t<_Self, _Fd>, __like_t<_Self, _BoundArgs>..., _CallArgs...> + constexpr _Result_t<_Self, _CallArgs...> operator()(this _Self&& __self, _CallArgs&&... __call_args) - noexcept(is_nothrow_invocable_v<__like_t<_Self, _Fd>, - __like_t<_Self, _BoundArgs>..., _CallArgs...>) + noexcept(_S_noexcept_invocable<_Self, _CallArgs...>()) { - return _S_call(__like_t<_Self, _Bind_front>(__self), + return _S_call(__like_t<_Self, _Binder>(__self), std::forward<_CallArgs>(__call_args)...); } #else template<typename... _CallArgs> requires true - constexpr - invoke_result_t<_Fd&, _BoundArgs&..., _CallArgs...> + constexpr _Result_t<_Binder&, _CallArgs...> operator()(_CallArgs&&... __call_args) & - noexcept(is_nothrow_invocable_v<_Fd&, _BoundArgs&..., _CallArgs...>) + noexcept(_S_noexcept_invocable<_Binder&, _CallArgs...>()) { return _S_call(*this, std::forward<_CallArgs>(__call_args)...); } template<typename... _CallArgs> requires true - constexpr - invoke_result_t<const _Fd&, const _BoundArgs&..., _CallArgs...> + constexpr _Result_t<const _Binder&, _CallArgs...> operator()(_CallArgs&&... __call_args) const & - noexcept(is_nothrow_invocable_v<const _Fd&, const _BoundArgs&..., - _CallArgs...>) + noexcept(_S_noexcept_invocable<const _Binder&, _CallArgs...>()) { return _S_call(*this, std::forward<_CallArgs>(__call_args)...); } template<typename... _CallArgs> requires true - constexpr - invoke_result_t<_Fd, _BoundArgs..., _CallArgs...> + constexpr _Result_t<_Binder&&, _CallArgs...> operator()(_CallArgs&&... __call_args) && - noexcept(is_nothrow_invocable_v<_Fd, _BoundArgs..., _CallArgs...>) + noexcept(_S_noexcept_invocable<_Binder&&, _CallArgs...>()) { return _S_call(std::move(*this), std::forward<_CallArgs>(__call_args)...); @@ -1033,11 +1043,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template<typename... _CallArgs> requires true - constexpr - invoke_result_t<const _Fd, const _BoundArgs..., _CallArgs...> + constexpr _Result_t<const _Binder&&, _CallArgs...> operator()(_CallArgs&&... __call_args) const && - noexcept(is_nothrow_invocable_v<const _Fd, const _BoundArgs..., - _CallArgs...>) + noexcept(_S_noexcept_invocable<const _Binder&&, _CallArgs...>()) { return _S_call(std::move(*this), std::forward<_CallArgs>(__call_args)...); @@ -1066,15 +1074,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION decltype(auto) _S_call(_Tp&& __g, _CallArgs&&... __call_args) { - if constexpr (sizeof...(_BoundArgs) == 1) - return std::invoke(std::forward<_Tp>(__g)._M_fd, - std::forward<_Tp>(__g)._M_bound_args, - std::forward<_CallArgs>(__call_args)...); - else - return _BoundArgsStorage::_S_apply_front( + if constexpr (sizeof...(_BoundArgs) > 1) + return _BoundArgsStorage::template _S_apply<_Back>( std::forward<_Tp>(__g)._M_fd, std::forward<_Tp>(__g)._M_bound_args, std::forward<_CallArgs>(__call_args)...); + else if constexpr (sizeof...(_BoundArgs) == 0) + return std::invoke(std::forward<_Tp>(__g)._M_fd, + std::forward<_CallArgs>(__call_args)...); + else if constexpr (_Back) // sizeof...(_BoundArgs) == 1 + return std::invoke(std::forward<_Tp>(__g)._M_fd, + std::forward<_CallArgs>(__call_args)..., + std::forward<_Tp>(__g)._M_bound_args); + else // !_Back && sizeof...(_BoundArgs) == 1 + return std::invoke(std::forward<_Tp>(__g)._M_fd, + std::forward<_Tp>(__g)._M_bound_args, + std::forward<_CallArgs>(__call_args)...); + } [[no_unique_address]] _Fd _M_fd; @@ -1082,8 +1098,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION }; template<typename _Fn, typename... _Args> - using _Bind_front_t = _Bind_front<decay_t<_Fn>, decay_t<_Args>...>; + using _Bind_front_t = _Binder<false, decay_t<_Fn>, decay_t<_Args>...>; + + // for zero bounds args behavior of bind_front and bind_back is the same, + // so reuse _Bind_front_t, i.e. _Binder<false, ...> + template<typename _Fn, typename... _Args> + using _Bind_back_t + = _Binder<(sizeof...(_Args) > 0), decay_t<_Fn>, decay_t<_Args>...>; +#endif // __cplusplus >= 202002L +#ifdef __cpp_lib_bind_front // C++ >= 20 /** Create call wrapper by partial application of arguments to function. * * The result of `std::bind_front(f, args...)` is a function object that @@ -1105,62 +1129,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #endif // __cpp_lib_bind_front #ifdef __cpp_lib_bind_back // C++ >= 23 - template<typename _Fd, typename... _BoundArgs> - struct _Bind_back - { - static_assert(is_move_constructible_v<_Fd>); - static_assert((is_move_constructible_v<_BoundArgs> && ...)); - - // First parameter is to ensure this constructor is never used - // instead of the copy/move constructor. - template<typename _Fn, typename... _Args> - explicit constexpr - _Bind_back(int, _Fn&& __fn, _Args&&... __args) - noexcept(__and_<is_nothrow_constructible<_Fd, _Fn>, - is_nothrow_constructible<_BoundArgs, _Args>...>::value) - : _M_fd(std::forward<_Fn>(__fn)), - _M_bound_args(__make_bound_args<_BoundArgs...>(std::forward<_Args>(__args)...)) - { static_assert(sizeof...(_Args) == sizeof...(_BoundArgs)); } - - template<typename _Self, typename... _CallArgs> - constexpr - invoke_result_t<__like_t<_Self, _Fd>, _CallArgs..., __like_t<_Self, _BoundArgs>...> - operator()(this _Self&& __self, _CallArgs&&... __call_args) - noexcept(is_nothrow_invocable_v<__like_t<_Self, _Fd>, - _CallArgs..., __like_t<_Self, _BoundArgs>...>) - { - return _S_call(__like_t<_Self, _Bind_back>(__self), - std::forward<_CallArgs>(__call_args)...); - } - - private: - using _BoundArgsStorage - // _BoundArgs are required to be move-constructible, so this is valid. - = decltype(__make_bound_args<_BoundArgs...>(std::declval<_BoundArgs>()...)); - - template<typename _Tp, typename... _CallArgs> - static constexpr - decltype(auto) - _S_call(_Tp&& __g, _CallArgs&&... __call_args) - { - if constexpr (sizeof...(_BoundArgs) == 1) - return std::invoke(std::forward<_Tp>(__g)._M_fd, - std::forward<_CallArgs>(__call_args)..., - std::forward<_Tp>(__g)._M_bound_args); - else - return _BoundArgsStorage::_S_apply_back( - std::forward<_Tp>(__g)._M_fd, - std::forward<_Tp>(__g)._M_bound_args, - std::forward<_CallArgs>(__call_args)...); - } - - [[no_unique_address]] _Fd _M_fd; - [[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 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..7141be282c0 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 @@ -57,7 +57,6 @@ test01() decltype(bind_back(std::declval<const F&>(), std::declval<const int&>(), std::declval<const float&>())) >); - // Reference wrappers should be handled: static_assert(!std::is_same_v< decltype(bind_back(std::declval<F>(), std::declval<int&>())), @@ -197,6 +196,21 @@ testCallArgs(Args... args) VERIFY( q.as_const && q.as_lvalue ); q = cg(std::move(ci)); VERIFY( q.as_const && ! q.as_lvalue ); + + struct S + { + int operator()(long, long, Args...) const { return 1; } + int operator()(int, void*, Args...) const { return 2; } + }; + + S s; + // literal zero can be converted to any pointer, so (int, void*) + // is best candidate + VERIFY( s(0, 0, args...) == 2 ); + // both arguments are bound to int&&, and no longer can be + // converted to pointer, (long, long) is only candidate + VERIFY( bind_back(s)(0, 0, args...) == 1 ); + VERIFY( bind_back(s, args...)(0, 0) == 1 ); } void diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc index de3ae47e37f..8e7dacf80df 100644 --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc @@ -50,4 +50,5 @@ int main() { std::move(std::as_const(g2))(); } -// { dg-error "no type named 'type' in 'struct std::invoke_result" "" { target c++23 } 0 } +// { dg-error "no type named 'type' in 'std::__conditional_t<false, std::invoke_result<" "" { target c++23 } 0 } +// { dg-error "no type named 'type' in 'std::__conditional_t<true, std::invoke_result<" "" { target c++23 } 0 } 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..93efb2efea4 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 @@ -196,6 +196,21 @@ testCallArgs(Args... args) VERIFY( q.as_const && q.as_lvalue ); q = cg(std::move(ci)); VERIFY( q.as_const && ! q.as_lvalue ); + + struct S + { + int operator()(Args..., long, long) const { return 1; } + int operator()(Args..., int, void*) const { return 2; } + }; + + S s; + // literal zero can be converted to any pointer, so (int, void*) + // is best candidate + VERIFY( s(args..., 0, 0) == 2 ); + // both arguments are bound to int&&, and no longer can be + // converted to pointer, (long, long) is only candidate + VERIFY( bind_front(s)(args..., 0, 0) == 1 ); + VERIFY( bind_front(s, args...)(0, 0) == 1 ); } void diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc index 6694322d67e..58832a61a7e 100644 --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc @@ -50,4 +50,4 @@ int main() { std::move(std::as_const(g2))(); } -// { dg-error "no type named 'type' in 'struct std::invoke_result" "" { target c++23 } 0 } +// { dg-error "no type named 'type' in 'std::__conditional_t<false, std::invoke_result<" "" { target c++23 } 0 } -- 2.50.1