libstdc++: Do not use deduced return type for std::forward_like

Message ID 20240802205054.545397-1-jwakely@redhat.com
State Superseded
Headers
Series libstdc++: Do not use deduced return type for std::forward_like |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm fail Patch failed to apply
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 fail Patch failed to apply

Commit Message

Jonathan Wakely Aug. 2, 2024, 8:50 p.m. UTC
  This isn't properly tested so I'm not pushing it, but I'm sharing it now
for comment.

-- >8 --

Inspired by https://github.com/llvm/llvm-project/issues/101614 this
replaces the deduced return type of std::forward_like with a type trait
to compute that type.

libstdc++-v3/ChangeLog:

	* include/bits/move.h (__forward_like_impl): New metafunction to
	compute the return type of std::forward_like.
	(forward_like, __like_t): Use it.
	* testsuite/20_util/forward_like/2_neg.cc: Adjust expected
	errors.
---
 libstdc++-v3/include/bits/move.h              | 45 +++++++++----------
 .../testsuite/20_util/forward_like/2_neg.cc   |  2 +-
 2 files changed, 23 insertions(+), 24 deletions(-)
  

Comments

Patrick Palka Aug. 2, 2024, 10:37 p.m. UTC | #1
On Fri, 2 Aug 2024, Jonathan Wakely wrote:

> This isn't properly tested so I'm not pushing it, but I'm sharing it now
> for comment.
> 
> -- >8 --
> 
> Inspired by https://github.com/llvm/llvm-project/issues/101614 this
> replaces the deduced return type of std::forward_like with a type trait
> to compute that type.
> 
> libstdc++-v3/ChangeLog:
> 
> 	* include/bits/move.h (__forward_like_impl): New metafunction to
> 	compute the return type of std::forward_like.
> 	(forward_like, __like_t): Use it.
> 	* testsuite/20_util/forward_like/2_neg.cc: Adjust expected
> 	errors.
> ---
>  libstdc++-v3/include/bits/move.h              | 45 +++++++++----------
>  .../testsuite/20_util/forward_like/2_neg.cc   |  2 +-
>  2 files changed, 23 insertions(+), 24 deletions(-)
> 
> diff --git a/libstdc++-v3/include/bits/move.h b/libstdc++-v3/include/bits/move.h
> index bb200c95964..15b7cd07fce 100644
> --- a/libstdc++-v3/include/bits/move.h
> +++ b/libstdc++-v3/include/bits/move.h
> @@ -88,31 +88,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  
>  #if __glibcxx_forward_like // C++ >= 23
>    template<typename _Tp, typename _Up>
> -  [[nodiscard]]
> -  constexpr decltype(auto)
> -  forward_like(_Up&& __x) noexcept
> -  {
> -    constexpr bool __as_rval = is_rvalue_reference_v<_Tp&&>;
> -
> -    if constexpr (is_const_v<remove_reference_t<_Tp>>)
> -      {
> -	using _Up2 = remove_reference_t<_Up>;
> -	if constexpr (__as_rval)
> -	  return static_cast<const _Up2&&>(__x);
> -	else
> -	  return static_cast<const _Up2&>(__x);
> -      }
> -    else
> -      {
> -	if constexpr (__as_rval)
> -	  return static_cast<remove_reference_t<_Up>&&>(__x);
> -	else
> -	  return static_cast<_Up&>(__x);
> -      }
> -  }
> +    struct __forward_like_impl
> +    {
> +#if _GLIBCXX_USE_BUILTIN_TRAIT(__remove_reference)
> +      template<typename _Xp> using __remove_ref_t = __remove_reference(_Xp);
> +#else
> +      template<typename _Xp> using __remove_ref_t = remove_reference_t<_Xp>;
> +#endif
> +      template<typename _Xp, typename _Yp>
> +	using _Copy_const = __conditional_t<is_const_v<_Xp>, const _Yp, _Yp>;
> +      template<typename _Xp, typename _Yp>
> +	using _Override_ref = __conditional_t<is_rvalue_reference_v<_Xp>,
> +					      __remove_ref_t<_Yp>&&, _Yp&>;

Moving these nested alias templates out to namespace scope improves
compile time and memory usage of forward_like by about 25% (since nested
templates are more expensive to instantiate).

However, the following implementation using a single partially
specialized class template is a further 20% faster and uses around
15% less memory:

-- >8 --

Subject: [PATCH] libstdc++: use concrete return type for std::forward_like

Inspired by https://github.com/llvm/llvm-project/issues/101614 this
inverts the relationship between forward_like and __like_t, so that
forward_like is defined in terms of __like_t and with a concrete return
type.  __like_t in turn is defined via partial specializations that
performs case analysis on the const- and reference-ness of T.

This turns out to be more SFINAE friendly and significantly cheaper
to compile than the previous implementation.

libstdc++-v3/ChangeLog:

	* include/bits/move.h (__like_impl): New metafunction.
	(__like_t): Redefine in terms of __like_impl.
	(forward_like): Redefine in terms of __like_t.
	* testsuite/20_util/forward_like/2_neg.cc: Don't expect
	error outside the immediate context anymore.
---
 libstdc++-v3/include/bits/move.h              | 46 +++++++++----------
 .../testsuite/20_util/forward_like/2_neg.cc   |  6 +--
 2 files changed, 25 insertions(+), 27 deletions(-)

diff --git a/libstdc++-v3/include/bits/move.h b/libstdc++-v3/include/bits/move.h
index bb200c95964..eaf4280d7ec 100644
--- a/libstdc++-v3/include/bits/move.h
+++ b/libstdc++-v3/include/bits/move.h
@@ -88,31 +88,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
 #if __glibcxx_forward_like // C++ >= 23
   template<typename _Tp, typename _Up>
-  [[nodiscard]]
-  constexpr decltype(auto)
-  forward_like(_Up&& __x) noexcept
-  {
-    constexpr bool __as_rval = is_rvalue_reference_v<_Tp&&>;
-
-    if constexpr (is_const_v<remove_reference_t<_Tp>>)
-      {
-	using _Up2 = remove_reference_t<_Up>;
-	if constexpr (__as_rval)
-	  return static_cast<const _Up2&&>(__x);
-	else
-	  return static_cast<const _Up2&>(__x);
-      }
-    else
-      {
-	if constexpr (__as_rval)
-	  return static_cast<remove_reference_t<_Up>&&>(__x);
-	else
-	  return static_cast<_Up&>(__x);
-      }
-  }
+  struct __like_impl; // _Tp must be a reference and _Up an lvalue reference
+
+  template<typename _Tp, typename _Up>
+  struct __like_impl<_Tp&, _Up&>
+  { using type = _Up&; };
+
+  template<typename _Tp, typename _Up>
+  struct __like_impl<const _Tp&, _Up&>
+  { using type = const _Up&; };
+
+  template<typename _Tp, typename _Up>
+  struct __like_impl<_Tp&&, _Up&>
+  { using type = _Up&&; };
+
+  template<typename _Tp, typename _Up>
+  struct __like_impl<const _Tp&&, _Up&>
+  { using type = const _Up&&; };
 
   template<typename _Tp, typename _Up>
-    using __like_t = decltype(std::forward_like<_Tp>(std::declval<_Up>()));
+    using __like_t = __like_impl<_Tp&&, _Up&>;
+
+  template<typename _Tp, typename _Up>
+  constexpr __like_t<_Tp, _Up>
+  forward_like(_Up&& __x) noexcept
+  { return static_cast<__like_t<_Tp, _Up>>(__x); }
 #endif
 
   /**
diff --git a/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc b/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
index ff835af1915..5dafa419a7e 100644
--- a/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
@@ -2,9 +2,7 @@
 
 #include <utility>
 
-auto x1 = std::forward_like<void>(1); // { dg-error "here" }
+auto x1 = std::forward_like<void>(1); // { dg-error "no match" }
 // { dg-error "forming reference to void" "" { target *-*-* } 0 }
-auto x2 = std::forward_like<void()const>(1); // { dg-error "here" }
+auto x2 = std::forward_like<void()const>(1); // { dg-error "no match" }
 // { dg-error "forming reference to qualified function" "" { target *-*-* } 0 }
-
-// { dg-prune-output "inconsistent deduction for auto return type" } // PR111484
  
Patrick Palka Aug. 2, 2024, 10:43 p.m. UTC | #2
On Fri, 2 Aug 2024, Patrick Palka wrote:

> On Fri, 2 Aug 2024, Jonathan Wakely wrote:
> 
> > This isn't properly tested so I'm not pushing it, but I'm sharing it now
> > for comment.
> > 
> > -- >8 --
> > 
> > Inspired by https://github.com/llvm/llvm-project/issues/101614 this
> > replaces the deduced return type of std::forward_like with a type trait
> > to compute that type.
> > 
> > libstdc++-v3/ChangeLog:
> > 
> > 	* include/bits/move.h (__forward_like_impl): New metafunction to
> > 	compute the return type of std::forward_like.
> > 	(forward_like, __like_t): Use it.
> > 	* testsuite/20_util/forward_like/2_neg.cc: Adjust expected
> > 	errors.
> > ---
> >  libstdc++-v3/include/bits/move.h              | 45 +++++++++----------
> >  .../testsuite/20_util/forward_like/2_neg.cc   |  2 +-
> >  2 files changed, 23 insertions(+), 24 deletions(-)
> > 
> > diff --git a/libstdc++-v3/include/bits/move.h b/libstdc++-v3/include/bits/move.h
> > index bb200c95964..15b7cd07fce 100644
> > --- a/libstdc++-v3/include/bits/move.h
> > +++ b/libstdc++-v3/include/bits/move.h
> > @@ -88,31 +88,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  
> >  #if __glibcxx_forward_like // C++ >= 23
> >    template<typename _Tp, typename _Up>
> > -  [[nodiscard]]
> > -  constexpr decltype(auto)
> > -  forward_like(_Up&& __x) noexcept
> > -  {
> > -    constexpr bool __as_rval = is_rvalue_reference_v<_Tp&&>;
> > -
> > -    if constexpr (is_const_v<remove_reference_t<_Tp>>)
> > -      {
> > -	using _Up2 = remove_reference_t<_Up>;
> > -	if constexpr (__as_rval)
> > -	  return static_cast<const _Up2&&>(__x);
> > -	else
> > -	  return static_cast<const _Up2&>(__x);
> > -      }
> > -    else
> > -      {
> > -	if constexpr (__as_rval)
> > -	  return static_cast<remove_reference_t<_Up>&&>(__x);
> > -	else
> > -	  return static_cast<_Up&>(__x);
> > -      }
> > -  }
> > +    struct __forward_like_impl
> > +    {
> > +#if _GLIBCXX_USE_BUILTIN_TRAIT(__remove_reference)
> > +      template<typename _Xp> using __remove_ref_t = __remove_reference(_Xp);
> > +#else
> > +      template<typename _Xp> using __remove_ref_t = remove_reference_t<_Xp>;
> > +#endif
> > +      template<typename _Xp, typename _Yp>
> > +	using _Copy_const = __conditional_t<is_const_v<_Xp>, const _Yp, _Yp>;
> > +      template<typename _Xp, typename _Yp>
> > +	using _Override_ref = __conditional_t<is_rvalue_reference_v<_Xp>,
> > +					      __remove_ref_t<_Yp>&&, _Yp&>;
> 
> Moving these nested alias templates out to namespace scope improves
> compile time and memory usage of forward_like by about 25% (since nested
> templates are more expensive to instantiate).
> 
> However, the following implementation using a single partially
> specialized class template is a further 20% faster and uses around
> 15% less memory:
> 
> -- >8 --
> 
> Subject: [PATCH] libstdc++: use concrete return type for std::forward_like
> 
> Inspired by https://github.com/llvm/llvm-project/issues/101614 this
> inverts the relationship between forward_like and __like_t, so that
> forward_like is defined in terms of __like_t and with a concrete return
> type.  __like_t in turn is defined via partial specializations that
> performs case analysis on the const- and reference-ness of T.
> 
> This turns out to be more SFINAE friendly and significantly cheaper
> to compile than the previous implementation.
> 
> libstdc++-v3/ChangeLog:
> 
> 	* include/bits/move.h (__like_impl): New metafunction.
> 	(__like_t): Redefine in terms of __like_impl.
> 	(forward_like): Redefine in terms of __like_t.
> 	* testsuite/20_util/forward_like/2_neg.cc: Don't expect
> 	error outside the immediate context anymore.
> ---
>  libstdc++-v3/include/bits/move.h              | 46 +++++++++----------
>  .../testsuite/20_util/forward_like/2_neg.cc   |  6 +--
>  2 files changed, 25 insertions(+), 27 deletions(-)
> 
> diff --git a/libstdc++-v3/include/bits/move.h b/libstdc++-v3/include/bits/move.h
> index bb200c95964..eaf4280d7ec 100644
> --- a/libstdc++-v3/include/bits/move.h
> +++ b/libstdc++-v3/include/bits/move.h
> @@ -88,31 +88,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  
>  #if __glibcxx_forward_like // C++ >= 23
>    template<typename _Tp, typename _Up>
> -  [[nodiscard]]
> -  constexpr decltype(auto)
> -  forward_like(_Up&& __x) noexcept
> -  {
> -    constexpr bool __as_rval = is_rvalue_reference_v<_Tp&&>;
> -
> -    if constexpr (is_const_v<remove_reference_t<_Tp>>)
> -      {
> -	using _Up2 = remove_reference_t<_Up>;
> -	if constexpr (__as_rval)
> -	  return static_cast<const _Up2&&>(__x);
> -	else
> -	  return static_cast<const _Up2&>(__x);
> -      }
> -    else
> -      {
> -	if constexpr (__as_rval)
> -	  return static_cast<remove_reference_t<_Up>&&>(__x);
> -	else
> -	  return static_cast<_Up&>(__x);
> -      }
> -  }
> +  struct __like_impl; // _Tp must be a reference and _Up an lvalue reference
> +
> +  template<typename _Tp, typename _Up>
> +  struct __like_impl<_Tp&, _Up&>
> +  { using type = _Up&; };
> +
> +  template<typename _Tp, typename _Up>
> +  struct __like_impl<const _Tp&, _Up&>
> +  { using type = const _Up&; };
> +
> +  template<typename _Tp, typename _Up>
> +  struct __like_impl<_Tp&&, _Up&>
> +  { using type = _Up&&; };
> +
> +  template<typename _Tp, typename _Up>
> +  struct __like_impl<const _Tp&&, _Up&>
> +  { using type = const _Up&&; };
>  
>    template<typename _Tp, typename _Up>
> -    using __like_t = decltype(std::forward_like<_Tp>(std::declval<_Up>()));
> +    using __like_t = __like_impl<_Tp&&, _Up&>;

Oops, this patch I sent has a bug, this should say
typename __like_impl<_Tp&&, _Up&>::type.  (The patch I
actually benchmarked didn't have this bug, phew :))

Lightly tested, full testing in progress.

-- >8 --

Subject: [PATCH] libstdc++: use concrete return type for std::forward_like

Inspired by https://github.com/llvm/llvm-project/issues/101614 this
inverts the relationship between forward_like and __like_t, so that
forward_like is defined in terms of __like_t and with a concrete return
type.  __like_t in turn is defined via partial specializations that
performs case analysis on the const- and reference-ness of T.

This turns out to be more SFINAE friendly and significantly cheaper
to compile than the previous implementation.

libstdc++-v3/ChangeLog:

	* include/bits/move.h (__like_impl): New metafunction.
	(__like_t): Redefine in terms of __like_impl.
	(forward_like): Redefine in terms of __like_t.
	* testsuite/20_util/forward_like/2_neg.cc: Don't expect
	error outside the immediate context anymore.
---
 libstdc++-v3/include/bits/move.h              | 46 +++++++++----------
 .../testsuite/20_util/forward_like/2_neg.cc   |  6 +--
 2 files changed, 25 insertions(+), 27 deletions(-)

diff --git a/libstdc++-v3/include/bits/move.h b/libstdc++-v3/include/bits/move.h
index bb200c95964..caab3992e4a 100644
--- a/libstdc++-v3/include/bits/move.h
+++ b/libstdc++-v3/include/bits/move.h
@@ -88,31 +88,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
 #if __glibcxx_forward_like // C++ >= 23
   template<typename _Tp, typename _Up>
-  [[nodiscard]]
-  constexpr decltype(auto)
-  forward_like(_Up&& __x) noexcept
-  {
-    constexpr bool __as_rval = is_rvalue_reference_v<_Tp&&>;
-
-    if constexpr (is_const_v<remove_reference_t<_Tp>>)
-      {
-	using _Up2 = remove_reference_t<_Up>;
-	if constexpr (__as_rval)
-	  return static_cast<const _Up2&&>(__x);
-	else
-	  return static_cast<const _Up2&>(__x);
-      }
-    else
-      {
-	if constexpr (__as_rval)
-	  return static_cast<remove_reference_t<_Up>&&>(__x);
-	else
-	  return static_cast<_Up&>(__x);
-      }
-  }
+  struct __like_impl; // _Tp must be a reference and _Up an lvalue reference
+
+  template<typename _Tp, typename _Up>
+  struct __like_impl<_Tp&, _Up&>
+  { using type = _Up&; };
+
+  template<typename _Tp, typename _Up>
+  struct __like_impl<const _Tp&, _Up&>
+  { using type = const _Up&; };
+
+  template<typename _Tp, typename _Up>
+  struct __like_impl<_Tp&&, _Up&>
+  { using type = _Up&&; };
+
+  template<typename _Tp, typename _Up>
+  struct __like_impl<const _Tp&&, _Up&>
+  { using type = const _Up&&; };
 
   template<typename _Tp, typename _Up>
-    using __like_t = decltype(std::forward_like<_Tp>(std::declval<_Up>()));
+    using __like_t = typename __like_impl<_Tp&&, _Up&>::type;
+
+  template<typename _Tp, typename _Up>
+  constexpr __like_t<_Tp, _Up>
+  forward_like(_Up&& __x) noexcept
+  { return static_cast<__like_t<_Tp, _Up>>(__x); }
 #endif
 
   /**
diff --git a/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc b/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
index ff835af1915..5dafa419a7e 100644
--- a/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
@@ -2,9 +2,7 @@
 
 #include <utility>
 
-auto x1 = std::forward_like<void>(1); // { dg-error "here" }
+auto x1 = std::forward_like<void>(1); // { dg-error "no match" }
 // { dg-error "forming reference to void" "" { target *-*-* } 0 }
-auto x2 = std::forward_like<void()const>(1); // { dg-error "here" }
+auto x2 = std::forward_like<void()const>(1); // { dg-error "no match" }
 // { dg-error "forming reference to qualified function" "" { target *-*-* } 0 }
-
-// { dg-prune-output "inconsistent deduction for auto return type" } // PR111484
  
Jonathan Wakely Aug. 2, 2024, 11:05 p.m. UTC | #3
On Fri, 2 Aug 2024 at 23:43, Patrick Palka <ppalka@redhat.com> wrote:
>
> On Fri, 2 Aug 2024, Patrick Palka wrote:
>
> > On Fri, 2 Aug 2024, Jonathan Wakely wrote:
> >
> > > This isn't properly tested so I'm not pushing it, but I'm sharing it now
> > > for comment.
> > >
> > > -- >8 --
> > >
> > > Inspired by https://github.com/llvm/llvm-project/issues/101614 this
> > > replaces the deduced return type of std::forward_like with a type trait
> > > to compute that type.
> > >
> > > libstdc++-v3/ChangeLog:
> > >
> > >     * include/bits/move.h (__forward_like_impl): New metafunction to
> > >     compute the return type of std::forward_like.
> > >     (forward_like, __like_t): Use it.
> > >     * testsuite/20_util/forward_like/2_neg.cc: Adjust expected
> > >     errors.
> > > ---
> > >  libstdc++-v3/include/bits/move.h              | 45 +++++++++----------
> > >  .../testsuite/20_util/forward_like/2_neg.cc   |  2 +-
> > >  2 files changed, 23 insertions(+), 24 deletions(-)
> > >
> > > diff --git a/libstdc++-v3/include/bits/move.h b/libstdc++-v3/include/bits/move.h
> > > index bb200c95964..15b7cd07fce 100644
> > > --- a/libstdc++-v3/include/bits/move.h
> > > +++ b/libstdc++-v3/include/bits/move.h
> > > @@ -88,31 +88,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >
> > >  #if __glibcxx_forward_like // C++ >= 23
> > >    template<typename _Tp, typename _Up>
> > > -  [[nodiscard]]
> > > -  constexpr decltype(auto)
> > > -  forward_like(_Up&& __x) noexcept
> > > -  {
> > > -    constexpr bool __as_rval = is_rvalue_reference_v<_Tp&&>;
> > > -
> > > -    if constexpr (is_const_v<remove_reference_t<_Tp>>)
> > > -      {
> > > -   using _Up2 = remove_reference_t<_Up>;
> > > -   if constexpr (__as_rval)
> > > -     return static_cast<const _Up2&&>(__x);
> > > -   else
> > > -     return static_cast<const _Up2&>(__x);
> > > -      }
> > > -    else
> > > -      {
> > > -   if constexpr (__as_rval)
> > > -     return static_cast<remove_reference_t<_Up>&&>(__x);
> > > -   else
> > > -     return static_cast<_Up&>(__x);
> > > -      }
> > > -  }
> > > +    struct __forward_like_impl
> > > +    {
> > > +#if _GLIBCXX_USE_BUILTIN_TRAIT(__remove_reference)
> > > +      template<typename _Xp> using __remove_ref_t = __remove_reference(_Xp);
> > > +#else
> > > +      template<typename _Xp> using __remove_ref_t = remove_reference_t<_Xp>;
> > > +#endif
> > > +      template<typename _Xp, typename _Yp>
> > > +   using _Copy_const = __conditional_t<is_const_v<_Xp>, const _Yp, _Yp>;
> > > +      template<typename _Xp, typename _Yp>
> > > +   using _Override_ref = __conditional_t<is_rvalue_reference_v<_Xp>,
> > > +                                         __remove_ref_t<_Yp>&&, _Yp&>;
> >
> > Moving these nested alias templates out to namespace scope improves
> > compile time and memory usage of forward_like by about 25% (since nested
> > templates are more expensive to instantiate).
> >
> > However, the following implementation using a single partially
> > specialized class template is a further 20% faster and uses around
> > 15% less memory:
> >
> > -- >8 --
> >
> > Subject: [PATCH] libstdc++: use concrete return type for std::forward_like
> >
> > Inspired by https://github.com/llvm/llvm-project/issues/101614 this
> > inverts the relationship between forward_like and __like_t, so that
> > forward_like is defined in terms of __like_t and with a concrete return
> > type.  __like_t in turn is defined via partial specializations that
> > performs case analysis on the const- and reference-ness of T.
> >
> > This turns out to be more SFINAE friendly and significantly cheaper
> > to compile than the previous implementation.
> >
> > libstdc++-v3/ChangeLog:
> >
> >       * include/bits/move.h (__like_impl): New metafunction.
> >       (__like_t): Redefine in terms of __like_impl.
> >       (forward_like): Redefine in terms of __like_t.
> >       * testsuite/20_util/forward_like/2_neg.cc: Don't expect
> >       error outside the immediate context anymore.
> > ---
> >  libstdc++-v3/include/bits/move.h              | 46 +++++++++----------
> >  .../testsuite/20_util/forward_like/2_neg.cc   |  6 +--
> >  2 files changed, 25 insertions(+), 27 deletions(-)
> >
> > diff --git a/libstdc++-v3/include/bits/move.h b/libstdc++-v3/include/bits/move.h
> > index bb200c95964..eaf4280d7ec 100644
> > --- a/libstdc++-v3/include/bits/move.h
> > +++ b/libstdc++-v3/include/bits/move.h
> > @@ -88,31 +88,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >
> >  #if __glibcxx_forward_like // C++ >= 23
> >    template<typename _Tp, typename _Up>
> > -  [[nodiscard]]
> > -  constexpr decltype(auto)
> > -  forward_like(_Up&& __x) noexcept
> > -  {
> > -    constexpr bool __as_rval = is_rvalue_reference_v<_Tp&&>;
> > -
> > -    if constexpr (is_const_v<remove_reference_t<_Tp>>)
> > -      {
> > -     using _Up2 = remove_reference_t<_Up>;
> > -     if constexpr (__as_rval)
> > -       return static_cast<const _Up2&&>(__x);
> > -     else
> > -       return static_cast<const _Up2&>(__x);
> > -      }
> > -    else
> > -      {
> > -     if constexpr (__as_rval)
> > -       return static_cast<remove_reference_t<_Up>&&>(__x);
> > -     else
> > -       return static_cast<_Up&>(__x);
> > -      }
> > -  }
> > +  struct __like_impl; // _Tp must be a reference and _Up an lvalue reference
> > +
> > +  template<typename _Tp, typename _Up>
> > +  struct __like_impl<_Tp&, _Up&>
> > +  { using type = _Up&; };
> > +
> > +  template<typename _Tp, typename _Up>
> > +  struct __like_impl<const _Tp&, _Up&>
> > +  { using type = const _Up&; };
> > +
> > +  template<typename _Tp, typename _Up>
> > +  struct __like_impl<_Tp&&, _Up&>
> > +  { using type = _Up&&; };
> > +
> > +  template<typename _Tp, typename _Up>
> > +  struct __like_impl<const _Tp&&, _Up&>
> > +  { using type = const _Up&&; };
> >
> >    template<typename _Tp, typename _Up>
> > -    using __like_t = decltype(std::forward_like<_Tp>(std::declval<_Up>()));
> > +    using __like_t = __like_impl<_Tp&&, _Up&>;
>
> Oops, this patch I sent has a bug, this should say
> typename __like_impl<_Tp&&, _Up&>::type.  (The patch I
> actually benchmarked didn't have this bug, phew :))
>
> Lightly tested, full testing in progress.

This is a really smart implementation. OK for trunk if it passes all tests.

And for gcc-14 after a bit of time on trunk.


>
> -- >8 --
>
> Subject: [PATCH] libstdc++: use concrete return type for std::forward_like
>
> Inspired by https://github.com/llvm/llvm-project/issues/101614 this
> inverts the relationship between forward_like and __like_t, so that
> forward_like is defined in terms of __like_t and with a concrete return
> type.  __like_t in turn is defined via partial specializations that
> performs case analysis on the const- and reference-ness of T.
>
> This turns out to be more SFINAE friendly and significantly cheaper
> to compile than the previous implementation.
>
> libstdc++-v3/ChangeLog:
>
>         * include/bits/move.h (__like_impl): New metafunction.
>         (__like_t): Redefine in terms of __like_impl.
>         (forward_like): Redefine in terms of __like_t.
>         * testsuite/20_util/forward_like/2_neg.cc: Don't expect
>         error outside the immediate context anymore.
> ---
>  libstdc++-v3/include/bits/move.h              | 46 +++++++++----------
>  .../testsuite/20_util/forward_like/2_neg.cc   |  6 +--
>  2 files changed, 25 insertions(+), 27 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/move.h b/libstdc++-v3/include/bits/move.h
> index bb200c95964..caab3992e4a 100644
> --- a/libstdc++-v3/include/bits/move.h
> +++ b/libstdc++-v3/include/bits/move.h
> @@ -88,31 +88,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
>  #if __glibcxx_forward_like // C++ >= 23
>    template<typename _Tp, typename _Up>
> -  [[nodiscard]]
> -  constexpr decltype(auto)
> -  forward_like(_Up&& __x) noexcept
> -  {
> -    constexpr bool __as_rval = is_rvalue_reference_v<_Tp&&>;
> -
> -    if constexpr (is_const_v<remove_reference_t<_Tp>>)
> -      {
> -       using _Up2 = remove_reference_t<_Up>;
> -       if constexpr (__as_rval)
> -         return static_cast<const _Up2&&>(__x);
> -       else
> -         return static_cast<const _Up2&>(__x);
> -      }
> -    else
> -      {
> -       if constexpr (__as_rval)
> -         return static_cast<remove_reference_t<_Up>&&>(__x);
> -       else
> -         return static_cast<_Up&>(__x);
> -      }
> -  }
> +  struct __like_impl; // _Tp must be a reference and _Up an lvalue reference
> +
> +  template<typename _Tp, typename _Up>
> +  struct __like_impl<_Tp&, _Up&>
> +  { using type = _Up&; };
> +
> +  template<typename _Tp, typename _Up>
> +  struct __like_impl<const _Tp&, _Up&>
> +  { using type = const _Up&; };
> +
> +  template<typename _Tp, typename _Up>
> +  struct __like_impl<_Tp&&, _Up&>
> +  { using type = _Up&&; };
> +
> +  template<typename _Tp, typename _Up>
> +  struct __like_impl<const _Tp&&, _Up&>
> +  { using type = const _Up&&; };
>
>    template<typename _Tp, typename _Up>
> -    using __like_t = decltype(std::forward_like<_Tp>(std::declval<_Up>()));
> +    using __like_t = typename __like_impl<_Tp&&, _Up&>::type;
> +
> +  template<typename _Tp, typename _Up>
> +  constexpr __like_t<_Tp, _Up>
> +  forward_like(_Up&& __x) noexcept
> +  { return static_cast<__like_t<_Tp, _Up>>(__x); }
>  #endif
>
>    /**
> diff --git a/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc b/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
> index ff835af1915..5dafa419a7e 100644
> --- a/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
> +++ b/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
> @@ -2,9 +2,7 @@
>
>  #include <utility>
>
> -auto x1 = std::forward_like<void>(1); // { dg-error "here" }
> +auto x1 = std::forward_like<void>(1); // { dg-error "no match" }
>  // { dg-error "forming reference to void" "" { target *-*-* } 0 }
> -auto x2 = std::forward_like<void()const>(1); // { dg-error "here" }
> +auto x2 = std::forward_like<void()const>(1); // { dg-error "no match" }
>  // { dg-error "forming reference to qualified function" "" { target *-*-* } 0 }
> -
> -// { dg-prune-output "inconsistent deduction for auto return type" } // PR111484
> --
> 2.46.0.39.g891ee3b9db
>
  

Patch

diff --git a/libstdc++-v3/include/bits/move.h b/libstdc++-v3/include/bits/move.h
index bb200c95964..15b7cd07fce 100644
--- a/libstdc++-v3/include/bits/move.h
+++ b/libstdc++-v3/include/bits/move.h
@@ -88,31 +88,30 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
 #if __glibcxx_forward_like // C++ >= 23
   template<typename _Tp, typename _Up>
-  [[nodiscard]]
-  constexpr decltype(auto)
-  forward_like(_Up&& __x) noexcept
-  {
-    constexpr bool __as_rval = is_rvalue_reference_v<_Tp&&>;
-
-    if constexpr (is_const_v<remove_reference_t<_Tp>>)
-      {
-	using _Up2 = remove_reference_t<_Up>;
-	if constexpr (__as_rval)
-	  return static_cast<const _Up2&&>(__x);
-	else
-	  return static_cast<const _Up2&>(__x);
-      }
-    else
-      {
-	if constexpr (__as_rval)
-	  return static_cast<remove_reference_t<_Up>&&>(__x);
-	else
-	  return static_cast<_Up&>(__x);
-      }
-  }
+    struct __forward_like_impl
+    {
+#if _GLIBCXX_USE_BUILTIN_TRAIT(__remove_reference)
+      template<typename _Xp> using __remove_ref_t = __remove_reference(_Xp);
+#else
+      template<typename _Xp> using __remove_ref_t = remove_reference_t<_Xp>;
+#endif
+      template<typename _Xp, typename _Yp>
+	using _Copy_const = __conditional_t<is_const_v<_Xp>, const _Yp, _Yp>;
+      template<typename _Xp, typename _Yp>
+	using _Override_ref = __conditional_t<is_rvalue_reference_v<_Xp>,
+					      __remove_ref_t<_Yp>&&, _Yp&>;
+      using type = _Override_ref<_Tp&&, _Copy_const<__remove_ref_t<_Tp>,
+						    __remove_ref_t<_Up>>>;
+    };
 
   template<typename _Tp, typename _Up>
-    using __like_t = decltype(std::forward_like<_Tp>(std::declval<_Up>()));
+    using __like_t = typename __forward_like_impl<_Tp, _Up>::type;
+
+  template<typename _Tp, typename _Up>
+    [[nodiscard]]
+    constexpr __like_t<_Tp, _Up>
+    forward_like(_Up&& __x) noexcept
+    { return static_cast<__like_t<_Tp, _Up>>(__x); }
 #endif
 
   /**
diff --git a/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc b/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
index ff835af1915..f6e98420a9a 100644
--- a/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/forward_like/2_neg.cc
@@ -7,4 +7,4 @@  auto x1 = std::forward_like<void>(1); // { dg-error "here" }
 auto x2 = std::forward_like<void()const>(1); // { dg-error "here" }
 // { dg-error "forming reference to qualified function" "" { target *-*-* } 0 }
 
-// { dg-prune-output "inconsistent deduction for auto return type" } // PR111484
+// { dg-prune-output "no matching function" }