On Fri, 5 Dec 2025, Tomasz Kaminski wrote:

> Just one small suggestion, otherwise LGTM.
> 
> On Fri, Dec 5, 2025 at 3:57 PM Patrick Palka <[email protected]> wrote:
>       Tested on x86_64-pc-linu-xgnu, does this look OK for trunk?
> 
>       -- >8 --
> 
>       Implement the cv-qual forwarding required by std::bind using deducing
>       this when available, instead of needing 4 operator() overloads.  Using
>       deducing this here is more complicated here than in other call
>       wrappers because std::bind is not really "perfect forwarding": it
>       doesn't forward value category, and along with const-ness it also
>       forwards volatile-ness (before C++20).
> 
>       The old implementation suffers from the same problem that other
>       SFINAE-friendly call wrappers have which is solved by using deducing
>       this (see p5.5 of the deducing this paper P0847R7).
> 
>               PR libstdc++/80564
> 
>       libstdc++-v3/ChangeLog:
> 
>               * include/std/functional (__cv_like): New.
>               (_Bind::_Res_type): Don't define when not needed.
>               (_Bind::__dependent): Likewise.
>               (_Bind::_Res_type_cv): Likewise.
>               (_Bind::operator()) [_GLIBCXX_EXPLICIT_THIS_PARAMETER]:
>               Define as two instead of four overloads using deducing
>               this.
>               * testsuite/20_util/bind/cv_quals_2.cc: Ignore SFINAE
>               diagnostics inside headers.
>               * testsuite/20_util/bind/ref_neg.cc: Likewise.
>               * testsuite/20_util/bind/cv_quals_4.cc: New test.
>       ---
>        libstdc++-v3/include/std/functional           | 81 +++++++++++++++++++
>        .../testsuite/20_util/bind/cv_quals_2.cc      |  3 +
>        .../testsuite/20_util/bind/cv_quals_4.cc      | 39 +++++++++
>        .../testsuite/20_util/bind/ref_neg.cc         |  1 +
>        4 files changed, 124 insertions(+)
>        create mode 100644 libstdc++-v3/testsuite/20_util/bind/cv_quals_4.cc
> 
>       diff --git a/libstdc++-v3/include/std/functional 
> b/libstdc++-v3/include/std/functional
>       index 1928a27d3fd6..9348356f03ce 100644
>       --- a/libstdc++-v3/include/std/functional
>       +++ b/libstdc++-v3/include/std/functional
>       @@ -495,6 +495,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        # define _GLIBCXX_DEPR_BIND
>        #endif
> 
>       +#if _GLIBCXX_EXPLICIT_THIS_PARAMETER
>       +  // Return a _Up that has the same cv-quals as _Tp.
>       +  template<typename _Tp, typename _Up>
>       +    struct __cv_like
>       +    { using type = _Up; };
>       +
>       +  template<typename _Tp, typename _Up>
>       +    struct __cv_like<const _Tp, _Up>
>       +    { using type = const _Up; };
>       +
>       +  template<typename _Tp, typename _Up>
>       +    struct __cv_like<volatile _Tp, _Up>
>       +    { using type = volatile _Up; };
>       +
>       +  template<typename _Tp, typename _Up>
>       +    struct __cv_like<const volatile _Tp, _Up>
>       +    { using type = const volatile _Up; };
>       +
>       +  template<typename _Tp, typename _Up>
>       +    using __cv_like_t = typename __cv_like<_Tp, _Up>::type;
>       +#endif
>       +
>          /// Type of the function object returned from bind().
>          template<typename _Signature>
>            class _Bind;
>       @@ -564,6 +586,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>               using _Res_type_impl
>                 = __invoke_result_t<_Fn&, _Mu_type<_BArgs, _CallArgs>&&...>;
> 
>       +#if !_GLIBCXX_EXPLICIT_THIS_PARAMETER
>              template<typename _CallArgs>
>               using _Res_type = _Res_type_impl<_Functor, _CallArgs, 
> _Bound_args...>;
> 
>       @@ -576,6 +599,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>                 typename __cv_quals<__dependent<_CallArgs>>::type,
>                 _CallArgs,
>                 typename __cv_quals<_Bound_args>::type...>;
>       +#endif
> 
>             public:
>              template<typename... _Args>
>       @@ -593,6 +617,62 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>              _Bind(const _Bind&) = default;
>              _Bind(_Bind&&) = default;
> 
>       +#if _GLIBCXX_EXPLICIT_THIS_PARAMETER
>       +# pragma GCC diagnostic push
>       +# pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
>       +# pragma GCC diagnostic ignored "-Wc++23-extensions" // deducing this
>       +      // Call unqualified
>       +      template<typename... _Args,
>       +              typename _Self,
>       +              typename _Self_nonref = typename 
> remove_reference<_Self>::type,
>       +              __enable_if_t<!is_volatile<_Self_nonref>::value, int> = 
> 0,
>       +              typename _Result
>       +                = _Res_type_impl<__cv_like_t<_Self_nonref, _Functor>,
>       +                                 tuple<_Args...>,
>       +                                 __cv_like_t<_Self_nonref, 
> _Bound_args>...>>
>       +       _GLIBCXX20_CONSTEXPR
>       +       _Result
>       +       operator()(this _Self&& __self, _Args&&... __args)
>       +       {
>       +         if constexpr (is_const<_Self_nonref>::value)
>       +           return static_cast<const _Bind&>(__self)
> 
> Once you have __cv_like_t, is there any reason to not use something like here:
>       using _Bind_ref_t = __cv_like_t<_Self_nonref, _Bind>&;
>         _Bind_ref_t(__self).
> I think we also need to use c-cast, if someone privately inherited from bind 
> specialization,
> for some reason. I thinking of version of overload:
> template<typename... Funcs>
> class Overload : Funcs....
> {
> public: 
>      Overload(Funcs... func) : Funcs(funcs)... {}
>      using Funcs::operator()....;
> };
> 
> And then used with bind.

Good catch, I keep forgetting we need a C-style cast with deducing
this.

Changes in v2:
- use a C-style cast instead of static_cast when casting explicit
  this parameter to the curret instantiation, and add test for that

Testing on x86_64-pc-ilnux-gnu in progress, OK for trunk once
passes?

-- >8 --

Subject: [PATCH] libstdc++: Use deducing this in std::bind when available
 [PR80564]

Implement the cv-qual forwarding required by std::bind using deducing
this when available, instead of needing 4 operator() overloads.  Using
deducing this here is more complicated here than in other call
wrappers because std::bind is not really "perfect forwarding": it
doesn't forward value category, and along with const-ness it also
forwards volatile-ness (before C++20).

The old implementation suffers from the same problem that other
SFINAE-friendly call wrappers have which is solved by using deducing
this (see p5.5 of the deducing this paper P0847R7).

        PR libstdc++/80564

libstdc++-v3/ChangeLog:

        * include/std/functional (__cv_like): New.
        (_Bind::_Res_type): Don't define when not needed.
        (_Bind::__dependent): Likewise.
        (_Bind::_Res_type_cv): Likewise.
        (_Bind::operator()) [_GLIBCXX_EXPLICIT_THIS_PARAMETER]:
        Define as two instead of four overloads using deducing
        this.
        * testsuite/20_util/bind/cv_quals_2.cc: Ignore SFINAE
        diagnostics inside headers.
        * testsuite/20_util/bind/ref_neg.cc: Likewise.
        * testsuite/20_util/bind/cv_quals_4.cc: New test.
---
 libstdc++-v3/include/std/functional           | 82 +++++++++++++++++++
 .../testsuite/20_util/bind/cv_quals_2.cc      |  3 +
 .../testsuite/20_util/bind/cv_quals_4.cc      | 50 +++++++++++
 .../testsuite/20_util/bind/ref_neg.cc         |  1 +
 4 files changed, 136 insertions(+)
 create mode 100644 libstdc++-v3/testsuite/20_util/bind/cv_quals_4.cc

diff --git a/libstdc++-v3/include/std/functional 
b/libstdc++-v3/include/std/functional
index 1928a27d3fd6..06ff32752d4a 100644
--- a/libstdc++-v3/include/std/functional
+++ b/libstdc++-v3/include/std/functional
@@ -495,6 +495,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 # define _GLIBCXX_DEPR_BIND
 #endif
 
+#if _GLIBCXX_EXPLICIT_THIS_PARAMETER
+  // Return a _Up that has the same cv-quals as _Tp.
+  template<typename _Tp, typename _Up> // _Up should be cv-unqualified
+    struct __cv_like
+    { using type = _Up; };
+
+  template<typename _Tp, typename _Up>
+    struct __cv_like<const _Tp, _Up>
+    { using type = const _Up; };
+
+  template<typename _Tp, typename _Up>
+    struct __cv_like<volatile _Tp, _Up>
+    { using type = volatile _Up; };
+
+  template<typename _Tp, typename _Up>
+    struct __cv_like<const volatile _Tp, _Up>
+    { using type = const volatile _Up; };
+
+  template<typename _Tp, typename _Up>
+    using __cv_like_t = typename __cv_like<_Tp, _Up>::type;
+#endif
+
   /// Type of the function object returned from bind().
   template<typename _Signature>
     class _Bind;
@@ -564,6 +586,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        using _Res_type_impl
          = __invoke_result_t<_Fn&, _Mu_type<_BArgs, _CallArgs>&&...>;
 
+#if !_GLIBCXX_EXPLICIT_THIS_PARAMETER
       template<typename _CallArgs>
        using _Res_type = _Res_type_impl<_Functor, _CallArgs, _Bound_args...>;
 
@@ -576,6 +599,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
          typename __cv_quals<__dependent<_CallArgs>>::type,
          _CallArgs,
          typename __cv_quals<_Bound_args>::type...>;
+#endif
 
      public:
       template<typename... _Args>
@@ -593,6 +617,63 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _Bind(const _Bind&) = default;
       _Bind(_Bind&&) = default;
 
+#if _GLIBCXX_EXPLICIT_THIS_PARAMETER
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
+# pragma GCC diagnostic ignored "-Wc++23-extensions" // deducing this
+      template<typename... _Args,
+              typename _Self,
+              typename _Self_nonref = typename remove_reference<_Self>::type,
+              __enable_if_t<!is_volatile<_Self_nonref>::value, int> = 0,
+              typename _Result
+                = _Res_type_impl<__cv_like_t<_Self_nonref, _Functor>,
+                                 tuple<_Args...>,
+                                 __cv_like_t<_Self_nonref, _Bound_args>...>>
+       _GLIBCXX20_CONSTEXPR
+       _Result
+       operator()(this _Self&& __self, _Args&&... __args)
+       {
+         using _Bind_ref = __cv_like_t<_Self_nonref, _Bind>&;
+         if constexpr (is_const<_Self_nonref>::value)
+           return _Bind_ref(__self)
+             .template __call_c<_Result>(std::forward_as_tuple
+                                         (std::forward<_Args>(__args)...),
+                                         _Bound_indexes());
+         else
+           return _Bind_ref(__self)
+             .template __call<_Result>(std::forward_as_tuple
+                                       (std::forward<_Args>(__args)...),
+                                       _Bound_indexes());
+       }
+
+# if defined(_GLIBCXX_VOLATILE_BIND)
+      template<typename... _Args,
+              typename _Self,
+              typename _Self_nonref = typename remove_reference<_Self>::type,
+              __enable_if_t<is_volatile<_Self_nonref>::value, int> = 0,
+              typename _Result
+                = _Res_type_impl<__cv_like_t<_Self_nonref, _Functor>,
+                                 tuple<_Args...>,
+                                 __cv_like_t<_Self_nonref, _Bound_args>...>>
+       _GLIBCXX_DEPR_BIND
+       _Result
+       operator()(this _Self&& __self, _Args&&... __args)
+       {
+         using _Bind_ref = __cv_like_t<_Self_nonref, _Bind>&;
+         if constexpr (is_const<_Self_nonref>::value)
+           return _Bind_ref(__self)
+             .template __call_c_v<_Result>(std::forward_as_tuple
+                                           (std::forward<_Args>(__args)...),
+                                           _Bound_indexes());
+         else
+           return _Bind_ref(__self)
+             .template __call_v<_Result>(std::forward_as_tuple
+                                         (std::forward<_Args>(__args)...),
+                                         _Bound_indexes());
+       }
+# endif
+# pragma GCC diagnostic pop
+#else
       // Call unqualified
       template<typename... _Args,
               typename _Result = _Res_type<tuple<_Args...>>>
@@ -642,6 +723,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
              _Bound_indexes());
        }
 #endif // volatile
+#endif
     };
 
   /// Type of the function object returned from bind<R>().
diff --git a/libstdc++-v3/testsuite/20_util/bind/cv_quals_2.cc 
b/libstdc++-v3/testsuite/20_util/bind/cv_quals_2.cc
index e4c348f7a3ca..6d37cc43fd3a 100644
--- a/libstdc++-v3/testsuite/20_util/bind/cv_quals_2.cc
+++ b/libstdc++-v3/testsuite/20_util/bind/cv_quals_2.cc
@@ -44,6 +44,9 @@ void test01()
                       // { dg-error "no match" "" { target c++20 } 43 }
 }
 
+// Ignore the reasons for deduction/substitution failure in the headers.
+// { dg-prune-output "no type named 'type' in 'struct std::enable_if<false" }
+
 int main()
 {
   test01();
diff --git a/libstdc++-v3/testsuite/20_util/bind/cv_quals_4.cc 
b/libstdc++-v3/testsuite/20_util/bind/cv_quals_4.cc
new file mode 100644
index 000000000000..f6e1a1ec66da
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/bind/cv_quals_4.cc
@@ -0,0 +1,50 @@
+// PR libstdc++/80564 - bind on SFINAE unfriendly generic lambda
+// { dg-do compile { target c++14 } }
+
+#include <functional>
+
+struct A
+{
+  template<typename T>
+  auto operator()(T&)
+  { }
+
+  template<typename T>
+  auto operator()(T&) const
+  { T::fail; }
+};
+
+void
+test01()
+{
+  A a;
+  std::bind(a, 0)(); // doesn't consider the const overload
+  std::bind<void>(a, 0)();
+}
+
+void
+test02()
+{
+  auto f = [] (auto& x) { x = 1; };
+  int i;
+  std::bind(f, i)(); // doesn't try const-invoking the lambda
+  std::bind<void>(f, i)();
+}
+
+#if __cpp_variadic_using
+template<typename... Ts>
+struct overloaded : private Ts...
+{
+  overloaded(Ts... ts) : Ts(ts)... { }
+  using Ts::operator()...;
+};
+
+void
+test03()
+{
+  A a;
+  auto f = std::bind(a, 0);
+  overloaded<decltype(f)> g(f);
+  g();
+}
+#endif
diff --git a/libstdc++-v3/testsuite/20_util/bind/ref_neg.cc 
b/libstdc++-v3/testsuite/20_util/bind/ref_neg.cc
index dd47c437d426..46cc4bb330e2 100644
--- a/libstdc++-v3/testsuite/20_util/bind/ref_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/bind/ref_neg.cc
@@ -51,6 +51,7 @@ void test02()
 // Ignore the reasons for deduction/substitution failure in the headers.
 // Arrange for the match to work on installed trees as well as build trees.
 // { dg-prune-output "no type named 'type' in 'struct std::__invoke_result" }
+// { dg-prune-output "no type named 'type' in 'struct std::enable_if<false" }
 
 int main()
 {
-- 
2.52.0.154.gf0ef5b6d9b

Reply via email to