On Fri, Dec 5, 2025 at 4:26 PM Patrick Palka <[email protected]> wrote:
> 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? > LGTM. > > -- >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 >
