This patch reworks construction and assigment of function_ref from
other function_ref specialization:
 * the constructor is implicit, and have rebinding semantic if
   signatures are compatible (__is_funcref_convertible is true)
 * the cosntructor is explicit and assigment is deleted, if
   signatures are not compatible, and created function_ref refers
   to function_ref.

The implementations moves the _M_ptrs  members to newly defined base
class __polyfunc::_Ref_base. This allows us to reuse existing __base_of
and __invoker_of accessor in the implementation (after befriending them).
The accessors functions are also now marked as constexpr.
Furthermore we move _M_init function there, making it instantiations
independent from callback signature.

libstdc++-v3/ChangeLog:

        * include/bits/cpyfunc_impl.h: (__polyfunc::__invoker_of)
        (__polyfunc::_base_of): Mark as constexpr.
        * include/bits/funcref_impl.h (__is_funcref_convertible): Define
        partial specializations.
        (std::function_ref): Add base class
        of type__polyfunc::_Ref_base. Befriend __invoker_of, __base_of,
        __is_invoker_convertible.
        (function_ref::_Base): Define.
        (function_ref::_M_init, function_ref::_M_ptrs): Move to base class.
        (function_ref::function_ref(_Fn&&)): Handle specializations of
        function_ref. Make conditionally explicit.
        (function_ref::function_ref): Init base class before _M_invoke
        consistently.
        (function_ref::operator=): Excluded compatible function_ref.
        * include/bits/funcwrap.h : (__polyfunc::__invoker_of)
        (__polyfunc::_base_of): Mark as constexpr.
        (__polyfunc::__is_function_ref_v, __is_funcref_convertible)
        (__polyfunc::_Ref_base): Define.
        * include/bits/mofunc_impl.h: (__polyfunc::__invoker_of)
        (__polyfunc::_base_of): Mark as constexpr.
        * testsuite/20_util/function_ref/conv.cc: Updated test to illustrate
        that double indirection is avoided.
---
Not targeting trunk, posting as implementation exprience.

Tested on x86_64-linux locally. *function_ref* test passed with all
language modes.

 libstdc++-v3/include/bits/cpyfunc_impl.h      |  4 +-
 libstdc++-v3/include/bits/funcref_impl.h      | 52 +++++++++-----
 libstdc++-v3/include/bits/funcwrap.h          | 37 +++++++++-
 libstdc++-v3/include/bits/mofunc_impl.h       |  4 +-
 .../testsuite/20_util/function_ref/conv.cc    | 72 +++++++++++++++----
 5 files changed, 132 insertions(+), 37 deletions(-)

diff --git a/libstdc++-v3/include/bits/cpyfunc_impl.h 
b/libstdc++-v3/include/bits/cpyfunc_impl.h
index f1918ddf87a..4a89b66f1cb 100644
--- a/libstdc++-v3/include/bits/cpyfunc_impl.h
+++ b/libstdc++-v3/include/bits/cpyfunc_impl.h
@@ -252,11 +252,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       typename _Invoker::__storage_func_t _M_invoke = nullptr;
 
       template<typename _Func>
-       friend auto&
+       friend constexpr auto&
        __polyfunc::__invoker_of(_Func&) noexcept;
 
       template<typename _Func>
-       friend auto&
+       friend constexpr auto&
        __polyfunc::__base_of(_Func&) noexcept;
 
       template<typename _Dst, typename _Src>
diff --git a/libstdc++-v3/include/bits/funcref_impl.h 
b/libstdc++-v3/include/bits/funcref_impl.h
index 44c992281be..0525944b193 100644
--- a/libstdc++-v3/include/bits/funcref_impl.h
+++ b/libstdc++-v3/include/bits/funcref_impl.h
@@ -48,6 +48,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                              noexcept(_Noex)>
       { using type = _Ret(_Args...) noexcept(_Noex); };
   } // namespace __polyfunc
+    
+  template<bool _Noex1, bool _Noex2, typename _Ret, typename... _Args>
+    constexpr bool __is_funcref_convertible<
+                     function_ref<_Ret(_Args...) _GLIBCXX_MOF_CV 
noexcept(_Noex1)>,
+                     function_ref<_Ret(_Args...) _GLIBCXX_MOF_CV 
noexcept(_Noex2)>>
+      = _Noex1 >= _Noex2;
   /// @endcond
 
   /**
@@ -67,11 +73,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _Res, typename... _ArgTypes, bool _Noex>
     class function_ref<_Res(_ArgTypes...) _GLIBCXX_MOF_CV
                       noexcept(_Noex)>
+    : __polyfunc::_Ref_base
     {
       static_assert(
        (std::__is_complete_or_unbounded(__type_identity<_ArgTypes>()) && ...),
        "each parameter type must be a complete class");
 
+      using _Base = __polyfunc::_Ref_base;
       using _Invoker = __polyfunc::_Invoker<_Noex, _Res, _ArgTypes...>;
       using _Signature = _Invoker::_Signature;
 
@@ -89,8 +97,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        function_ref(_Fn* __fn) noexcept
        {
          __glibcxx_assert(__fn != nullptr);
-         _M_invoke = _Invoker::template _S_ptrs<_Fn*>();
          _M_init(__fn);
+         _M_invoke = _Invoker::template _S_ptrs<_Fn*>();
        }
 
       /// Target and bound object is object referenced by parameter.
@@ -103,11 +111,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
               // constant expression (above constructor is not constexpr).
               && (!is_function_v<_Vt>)
               && __is_invocable_using<_Vt _GLIBCXX_MOF_CV&>
-       constexpr
+       constexpr explicit(!__is_funcref_convertible<remove_cv_t<_Vt>, 
function_ref>)
        function_ref(_Fn&& __f) noexcept
        {
-         _M_invoke = _Invoker::template _S_ptrs<_Vt _GLIBCXX_MOF_CV&>();
-         _M_init(std::addressof(__f));
+         using _Vd = remove_cv_t<_Vt>;
+         if constexpr (__is_funcref_convertible<remove_cv_t<_Vt>, 
function_ref>)
+           {
+             _Base::operator=(__polyfunc::__base_of(__f));
+             _M_invoke = __polyfunc::__invoker_of(__f);
+           }
+         else
+           {
+             using _Tr = _Vt _GLIBCXX_MOF_CV&;
+             _M_init(std::addressof(__f));
+             _M_invoke = _Invoker::template _S_ptrs<_Tr>();
+           }
        }
 
       // _GLIBCXX_RESOLVE_LIB_DEFECTS
@@ -122,8 +140,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
          if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>)
            static_assert(__fn != nullptr);
 
-         _M_invoke = &_Invoker::template _S_nttp<__fn>;
          _M_ptrs._M_obj = nullptr;
+         _M_invoke = &_Invoker::template _S_nttp<__fn>;
        }
 
       /// Target object is equivalent to std::bind_front<_fn>(std::ref(__ref)).
@@ -139,13 +157,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            static_assert(__fn != nullptr);
 
          using _Tr = _Td _GLIBCXX_MOF_CV&;
+         _M_init(std::addressof(__ref));
          if constexpr (is_member_pointer_v<_Fn> && is_lvalue_reference_v<_Tr>)
            // N.B. invoking member pointer on lvalue produces the same effects,
            // as invoking it on pointer to that lvalue.
            _M_invoke = &_Invoker::template _S_bind_ptr<__fn, _Td 
_GLIBCXX_MOF_CV>;
          else
            _M_invoke = &_Invoker::template _S_bind_ref<__fn, _Tr>;
-         _M_init(std::addressof(__ref));
        }
 
       /// Target object is equivalent to std::bind_front<_fn>(__ptr).
@@ -161,12 +179,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
          if constexpr (is_member_pointer_v<_Fn>)
            __glibcxx_assert(__ptr != nullptr);
 
-         _M_invoke = &_Invoker::template _S_bind_ptr<__fn, _Td 
_GLIBCXX_MOF_CV>;
          _M_init(__ptr);
+         _M_invoke = &_Invoker::template _S_bind_ptr<__fn, _Td 
_GLIBCXX_MOF_CV>;
        }
 
       template<typename _Tp>
        requires (!is_same_v<_Tp, function_ref>)
+              && (!__is_funcref_convertible<_Tp, function_ref>)
               && (!is_pointer_v<_Tp>) && (!__is_nontype_v<_Tp>)
        function_ref&
        operator=(_Tp) = delete;
@@ -182,18 +201,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       { return _M_invoke(_M_ptrs, std::forward<_ArgTypes>(__args)...); }
 
     private:
-      template<typename _Tp>
-       constexpr void
-       _M_init(_Tp* __ptr) noexcept
-       {
-         if constexpr (is_function_v<_Tp>)
-           _M_ptrs._M_func = reinterpret_cast<void(*)()>(__ptr);
-         else
-           _M_ptrs._M_obj = __ptr;
-       }
-
       typename _Invoker::__ptrs_func_t _M_invoke;
-      __polyfunc::_Ptrs _M_ptrs;
+
+      template<typename _Func>
+       friend constexpr auto&
+       __polyfunc::__invoker_of(_Func&) noexcept;
+
+      template<typename _Func>
+       friend constexpr auto&
+       __polyfunc::__base_of(_Func&) noexcept;
     };
 
 #undef _GLIBCXX_MOF_CV
diff --git a/libstdc++-v3/include/bits/funcwrap.h 
b/libstdc++-v3/include/bits/funcwrap.h
index 67fd591e963..8ba02078f15 100644
--- a/libstdc++-v3/include/bits/funcwrap.h
+++ b/libstdc++-v3/include/bits/funcwrap.h
@@ -223,12 +223,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      using _Invoker = _Base_invoker<_Noex, remove_cv_t<_Ret>, 
__param_t<_Args>...>;
 
    template<typename _Func>
-     auto&
+     constexpr auto&
      __invoker_of(_Func& __f) noexcept
      { return __f._M_invoke; }
 
    template<typename _Func>
-     auto&
+     constexpr auto&
      __base_of(_Func& __f) noexcept
      { return static_cast<__like_t<_Func&, typename _Func::_Base>>(__f); }
 
@@ -530,6 +530,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   /// @cond undocumented
   namespace __polyfunc
   {
+    struct _Ref_base
+    {
+      template<typename _Tp>
+       constexpr void
+       _M_init(_Tp* __ptr) noexcept
+       {
+         if constexpr (is_function_v<_Tp>)
+           _M_ptrs._M_func = reinterpret_cast<void(*)()>(__ptr);
+         else
+           _M_ptrs._M_obj = __ptr;
+       }
+
+      _Ptrs _M_ptrs;
+    };
+
     template<typename _Sig>
       struct __skip_first_arg;
 
@@ -575,6 +590,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     function_ref(nontype_t<__f>, _Tp&&)
       -> function_ref<remove_pointer_t<_SignaturePtr>>;
 
+  /// @cond undocumented
+  template<typename _Tp>
+    constexpr bool __is_function_ref_v = false;
+  template<typename _Tp>
+    constexpr bool __is_function_ref_v<function_ref<_Tp>> = true;
+
+  // Partial specializations are defined in bits/funcref_impl.h
+  template<typename _Func1, typename _Func2>
+    constexpr bool __is_funcref_convertible = false;
+
+  // Additional partial specializations are defined in bits/funcref_impl.h
+  template<bool _Noex1, bool _Noex2, typename _Ret, typename... _Args>
+    constexpr bool __is_funcref_convertible<
+                     function_ref<_Ret(_Args...) const noexcept(_Noex1)>,
+                     function_ref<_Ret(_Args...) noexcept(_Noex2)>>
+      = _Noex1 >= _Noex2;
+  /// @endcond
+
 #endif // __glibcxx_function_ref
 
 _GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/include/bits/mofunc_impl.h 
b/libstdc++-v3/include/bits/mofunc_impl.h
index 468e6855fc6..222e1782d14 100644
--- a/libstdc++-v3/include/bits/mofunc_impl.h
+++ b/libstdc++-v3/include/bits/mofunc_impl.h
@@ -236,11 +236,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       typename _Invoker::__storage_func_t _M_invoke = nullptr;
 
       template<typename _Func>
-       friend auto&
+       friend constexpr auto&
        __polyfunc::__invoker_of(_Func&) noexcept;
 
       template<typename _Func>
-       friend auto&
+       friend constexpr auto&
        __polyfunc::__base_of(_Func&) noexcept;
 
       template<typename _Dst, typename _Src>
diff --git a/libstdc++-v3/testsuite/20_util/function_ref/conv.cc 
b/libstdc++-v3/testsuite/20_util/function_ref/conv.cc
index 7606d265f98..78cade26285 100644
--- a/libstdc++-v3/testsuite/20_util/function_ref/conv.cc
+++ b/libstdc++-v3/testsuite/20_util/function_ref/conv.cc
@@ -36,6 +36,38 @@ static_assert( std::is_same_v<std::function_ref<void(int 
const[5])>,
 static_assert( std::is_same_v<std::function_ref<void(FuncType)>,
                              std::function_ref<void(FuncType*)>>);
 
+// Compatible signatures, conversion is implicit 
+static_assert( std::is_convertible_v<std::function_ref<int(long) const 
noexcept>,
+                                    std::function_ref<int(long) noexcept>> );
+static_assert( std::is_convertible_v<std::function_ref<int(long) const 
noexcept>,
+                                    std::function_ref<int(long) const>> );
+static_assert( std::is_convertible_v<std::function_ref<int(long) const 
noexcept>,
+                                    std::function_ref<int(long)>> );
+static_assert( std::is_assignable_v<std::function_ref<int(long) noexcept>&,
+                                   std::function_ref<int(long) const 
noexcept>> );
+static_assert( std::is_assignable_v<std::function_ref<int(long) const>&,
+                                   std::function_ref<int(long) const 
noexcept>> );
+static_assert( std::is_assignable_v<std::function_ref<int(long)>&,
+                                   std::function_ref<int(long) const 
noexcept>> );
+
+// Incompatible signatures, conversion is explicit
+static_assert( !std::is_convertible_v<std::function_ref<int(long)>,
+                                     std::function_ref<int(long) const>> );
+static_assert( !std::is_convertible_v<std::function_ref<int(long)>,
+                                     std::function_ref<int(int)>> );
+static_assert( !std::is_convertible_v<std::function_ref<int(long)>,
+                                     std::function_ref<long(long)>> );
+static_assert( !std::is_convertible_v<std::function_ref<int(long)>,
+                                     std::function_ref<long(int)>> );
+static_assert( !std::is_assignable_v<std::function_ref<int(long) const>&,
+                                    std::function_ref<int(long)>> );
+static_assert( !std::is_assignable_v<std::function_ref<int(int)>&,
+                                    std::function_ref<int(long)>> );
+static_assert( !std::is_assignable_v<std::function_ref<long(long)>&,
+                                    std::function_ref<int(long)>> );
+static_assert( !std::is_assignable_v<std::function_ref<long(int)>&,
+                                    std::function_ref<int(long)>> );
+
 // The C++26 [func.wrap.general] p2 does not currently cover funciton_ref,
 // so we make extra copies of arguments.
 
@@ -54,26 +86,40 @@ test01()
   VERIFY( r2c(c) == 2 );
 
   std::function_ref<int(CountedArg) const> r3r(r1);
-  VERIFY( r3r(c) == 2 );
+  VERIFY( r3r(c) == 1 );
   std::function_ref<int(CountedArg) const> r3m(m1);
   VERIFY( r3m(c) == 2 );
   std::function_ref<int(CountedArg) const> r3c(c1);
   VERIFY( r3c(c) == 2 );
 
-  std::function_ref<int(CountedArg)> r4r(r1);
-  VERIFY( r4r(c) == 2 );
-  std::function_ref<int(CountedArg)> r4m(m1);
+  std::function_ref<int(CountedArg) noexcept> r4r(r1);
+  VERIFY( r4r(c) == 1 );
+  std::function_ref<int(CountedArg) noexcept> r4m(m1);
   VERIFY( r4m(c) == 2 );
-  std::function_ref<int(CountedArg)> r4c(c1);
+  std::function_ref<int(CountedArg) noexcept> r4c(c1);
   VERIFY( r4c(c) == 2 );
 
+  std::function_ref<int(CountedArg)> r5r(r1);
+  VERIFY( r5r(c) == 1 );
+  std::function_ref<int(CountedArg)> r5m(m1);
+  VERIFY( r5m(c) == 2 );
+  std::function_ref<int(CountedArg)> r5c(c1);
+  VERIFY( r5c(c) == 2 );
+
+  r3r = r1;
+  VERIFY( r3r(c) == 1 );
+  r4r = r1;
+  VERIFY( r4r(c) == 1 );
+  r5r = r1;
+  VERIFY( r5r(c) == 1 );
+
   // Incompatible signatures
-  std::function_ref<long(CountedArg) const noexcept> r5r(r1);
-  VERIFY( r5r(c) == 2 );
-  std::function_ref<long(CountedArg) const noexcept> r5m(m1);
-  VERIFY( r5r(c) == 2 );
-  std::function_ref<long(CountedArg) const noexcept> r5c(c1);
-  VERIFY( r5r(c) == 2 );
+  std::function_ref<long(CountedArg) const noexcept> r6r(r1);
+  VERIFY( r6r(c) == 2 );
+  std::function_ref<long(CountedArg) const noexcept> r6m(m1);
+  VERIFY( r6r(c) == 2 );
+  std::function_ref<long(CountedArg) const noexcept> r6c(c1);
+  VERIFY( r6r(c) == 2 );
 }
 
 void
@@ -110,7 +156,7 @@ test03()
   // Call const overload as std::function_ref<int(CountedArg) const>
   // inside std::function_ref<int(CountedArg)> would do.
   std::function_ref<int(CountedArg)> r2(r1);
-  VERIFY( r2(c) == 1002 );
+  VERIFY( r2(c) == 1001 );
   std::move_only_function<int(CountedArg)> m2(r1);
   VERIFY( m2(c) == 1002 );
 
@@ -234,7 +280,7 @@ test07()
   std::function_ref<int()> r3r(r1);
   VERIFY( r3r() == 2 );
   r1 = f1;
-  VERIFY( r3r() == 1 ); // converting-constructor
+  VERIFY( r3r() == 2 ); // rebinding constructor
 
   std::function_ref<int()> r3m(m1);
   VERIFY( r3m() == 2 );
-- 
2.51.0

Reply via email to