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

Reply via email to