This patch uses the additional provisions from LWG 4264, to avoid double
indirection when function_ref is constructed from move_only_function or
copyable_function with compatible signature. The details of compatible
signatures follows the move_only_function as described in
r16-617-g708d40ff109c6e49d02b684a368571722a160af8.

This requires ability to retrive an invoker accepting an _Ptrs, from the owning
wrappers that use const _Storage&. This is achieved by additional 
_Op::_PtrInvoker
operation that stores the invoker into __target._M_ptrs._M_func. In consequence
the _M_init for _Mo_base and _Cpy_base now accepts additional template parameter
designating ptr invoker.

The conversion is performed by _Ref_base::_M_adapt function, to uses
_Op::_Address to retrive pointer to stored object, and then returns pointer to
ptr invoker retrived using _Op::_PtrInvoke.

As constructing function_ref from empty move_only_function/copyable_function
is well-defined, but produces functor for wich any invocation is UB, we need
to thread this case specially, and _M_manage is not usable for such object.
When source is empty owning wrapper, we instad set the _M_invoke to nullptr
direclty, which gives the same effects.

As retriving the invoker requires reintepret_cast from void(*) to appropariate
function type, the undesirable consequence of this patch is that constructing
function_ref from usable in constant expression reference to non-empty
move_only_function or copyable_function is not constant expression. The 
constructor
is marked constexpr, but we do not specify when invocation is compile time,
at move_only_function/copyable_function cannot be cosntructed at compile time.

        PR libstdc++/119126

libstdc++-v3/ChangeLog:

        * include/bits/funcwrap.h (_Op::_PtrInvoke, _Manager::_S_create)
        (_Manager::_S_with_invoker, _Ref_base::_M_adapt): Define.
        (_Manager::_S_select): Make private.
        (_Manager::_S_func, _Manager::_S_trivial, _Manger::_S_local)
        (_Manager::_S_ptr): Add unreachable case _Op::_PtrInvoke.
        (_Mo_base::_M_init, _Cpy_base::_M_init): Accept ptr invoke as
        template paramter.
        (_Mo_base::_Ref_base) [__glibcxx_function_ref]: Declare as friend.
        * include/bits/funcref_impl.h:
        * include/bits/mofunc_impl.h (move_only_function::_M_init): Define.
        (move_only_function::move_only_function): Adjust to use _M_init.
        * include/bits/cpyfunc_impl.h (coyable_function::_M_init): Define.
        (coyable_function::coyable_function): Adjust to use _M_init.
        * testsuite/20_util/function_ref/conv.cc: Adjust test to reflect
        lack of double indirection. Test to check if right object is
        referenced.
---
 libstdc++-v3/include/bits/cpyfunc_impl.h      | 20 +++---
 libstdc++-v3/include/bits/funcref_impl.h      | 16 +++++
 libstdc++-v3/include/bits/funcwrap.h          | 59 ++++++++++++++++--
 libstdc++-v3/include/bits/mofunc_impl.h       | 20 +++---
 .../testsuite/20_util/function_ref/conv.cc    | 62 +++++++++++++++++--
 5 files changed, 151 insertions(+), 26 deletions(-)

diff --git a/libstdc++-v3/include/bits/cpyfunc_impl.h 
b/libstdc++-v3/include/bits/cpyfunc_impl.h
index 7ba74ed8a8d..5a802310cdd 100644
--- a/libstdc++-v3/include/bits/cpyfunc_impl.h
+++ b/libstdc++-v3/include/bits/cpyfunc_impl.h
@@ -117,11 +117,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            }
 
          if constexpr (!__is_polymorphic_function_v<_Vt>
-                         || !__polyfunc::__is_invoker_convertible<_Vt, 
copyable_function>())
-           {
-             _M_init<_Vt>(std::forward<_Fn>(__f));
-             _M_invoke = _Invoker::template _S_storage<_Vt 
_GLIBCXX_MOF_INV_QUALS>();
-           }
+              || !__polyfunc::__is_invoker_convertible<_Vt, 
copyable_function>())
+           _M_init<_Vt>(std::forward<_Fn>(__f));
          else if constexpr (is_lvalue_reference_v<_Fn>)
            {
              _M_copy(__polyfunc::__base_of(__f));
@@ -141,7 +138,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        explicit
        copyable_function(in_place_type_t<_Tp>, _Args&&... __args)
        noexcept(_S_nothrow_init<_Tp, _Args...>())
-       : _M_invoke(_Invoker::template _S_storage<_Tp _GLIBCXX_MOF_INV_QUALS>())
        {
          static_assert(is_same_v<decay_t<_Tp>, _Tp>);
          static_assert(is_copy_constructible_v<_Tp>);
@@ -156,7 +152,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        copyable_function(in_place_type_t<_Tp>, initializer_list<_Up> __il,
                           _Args&&... __args)
        noexcept(_S_nothrow_init<_Tp, initializer_list<_Up>&, _Args...>())
-       : _M_invoke(_Invoker::template _S_storage<_Tp _GLIBCXX_MOF_INV_QUALS>())
        {
          static_assert(is_same_v<decay_t<_Tp>, _Tp>);
          static_assert(is_copy_constructible_v<_Tp>);
@@ -245,6 +240,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       { return __x._M_invoke == nullptr; }
 
     private:
+      template<typename _Td, typename... _Args>
+       void
+       _M_init(_Args&&... __args)
+       noexcept(_S_nothrow_init<_Td, _Args...>())
+       {
+         using _Tr = _Td _GLIBCXX_MOF_INV_QUALS;
+         _Base::_M_init<_Invoker::template _S_ptrs<_Tr>(), _Td>(
+           std::forward<_Args>(__args)...);
+         _M_invoke = _Invoker::template _S_storage<_Tr>();
+       }
+
       typename _Invoker::__storage_func_t _M_invoke = nullptr;
 
       template<typename _Func>
diff --git a/libstdc++-v3/include/bits/funcref_impl.h 
b/libstdc++-v3/include/bits/funcref_impl.h
index f3ec765b953..100e45432ac 100644
--- a/libstdc++-v3/include/bits/funcref_impl.h
+++ b/libstdc++-v3/include/bits/funcref_impl.h
@@ -105,12 +105,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        function_ref(_Fn&& __f) noexcept
        {
          using _Vd = remove_cv_t<_Vt>;
+         if constexpr (__is_polymorphic_function_v<_Vd>)
+           if (__f == nullptr)
+           {
+             // Cosntructing function_ref from empty move_only_function or
+             // copyable_function has well-defined behavior and it produces
+             // a object, that have UB when invoked. This gives same affect.
+             _M_invoke = nullptr;
+             return;
+           }
+
          if constexpr (__is_function_ref_v<_Vd>
               && __polyfunc::__is_invoker_convertible<_Vd, function_ref>())
            {
              _Base::operator=(__polyfunc::__base_of(__f));
              _M_invoke = __polyfunc::__invoker_of(__f);
            }
+         else if constexpr (__is_polymorphic_function_v<_Vd>
+              && __polyfunc::__is_invoker_convertible<_Vd, function_ref>())
+           {
+             auto* __erasedInvoke = this->_M_adapt(__polyfunc::__base_of(__f));
+             _M_invoke = 
reinterpret_cast<_Invoker::__ptrs_func_t>(__erasedInvoke);
+           }
          else
            {
              using _Tr = _Vt _GLIBCXX_MOF_CV&;
diff --git a/libstdc++-v3/include/bits/funcwrap.h 
b/libstdc++-v3/include/bits/funcwrap.h
index 1c1ba637689..6e5806077d2 100644
--- a/libstdc++-v3/include/bits/funcwrap.h
+++ b/libstdc++-v3/include/bits/funcwrap.h
@@ -237,8 +237,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        // copies entity stored in *__src to __target, supported only if
        // _ProvideCopy is specified.
        _Copy,
-       // destroys entity stored in __target, __src is ignoring
+       // destroys entity stored in __target, __src is ignored
        _Destroy,
+       // saves address of invoker accepting _Ptrs to __target._M_ptrs._M_func,
+       // __src is ignored
+       _PtrInvoke,
      };
 
     // A function that performs operation __op on the __target and possibly 
__src.
@@ -247,6 +250,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     // The no-op manager function for objects with no target.
     static void _S_empty(_Op, _Storage&, const _Storage*) noexcept { }
 
+    template<auto _Invoke, bool _ProvideCopy, typename _Tp>
+      consteval static auto
+      _S_create()
+      { return &_S_with_invoker<_Invoke, _S_select<_ProvideCopy, _Tp>()>; }
+
+  private:
+    template<auto _Invoke, auto _Manage>
+      static void
+      _S_with_invoker(_Op __op, _Storage& __target, const _Storage* __src)
+      noexcept(noexcept(_Manage(__op, __target, __src)))
+      {
+       if (__op == _Op::_PtrInvoke)
+         __target._M_ptrs._M_func = reinterpret_cast<void(*)()>(_Invoke);
+       else
+         _Manage(__op, __target, __src);
+      }
+
     template<bool _ProvideCopy, typename _Tp>
       consteval static auto
       _S_select()
@@ -261,7 +281,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
          return &_S_local<_ProvideCopy, _Tp>;
       }
 
-   private:
      static void
      _S_func(_Op __op, _Storage& __target, const _Storage* __src) noexcept
      {
@@ -274,6 +293,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
           return;
         case _Op::_Destroy:
           return;
+        case _Op::_PtrInvoke:
+          __builtin_unreachable();
        }
      }
 
@@ -294,6 +315,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
           return;
         case _Op::_Destroy:
           return;
+        case _Op::_PtrInvoke:
+          __builtin_unreachable();
        }
      }
 
@@ -325,6 +348,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                 return;
               }
             __builtin_unreachable();
+          case _Op::_PtrInvoke:
+            __builtin_unreachable();
         }
        }
 
@@ -350,6 +375,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                 return;
               }
             __builtin_unreachable();
+          case _Op::_PtrInvoke:
+            __builtin_unreachable();
          }
        }
    };
@@ -369,13 +396,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        _S_nothrow_init() noexcept
        { return _Storage::_S_nothrow_init<_Tp, _Args...>(); }
 
-     template<typename _Tp, typename... _Args>
+     template<auto _Invoke, typename _Tp, typename... _Args>
        void
        _M_init(_Args&&... __args)
        noexcept(_S_nothrow_init<_Tp, _Args...>())
        {
         _M_storage._M_init<_Tp>(std::forward<_Args>(__args)...);
-        _M_manage = _Manager::_S_select<false, _Tp>();
+        _M_manage = _Manager::_S_create<_Invoke, false, _Tp>();
        }
 
      void
@@ -427,6 +454,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #ifdef __glibcxx_copyable_function // C++ >= 26 && HOSTED
      friend class _Cpy_base;
 #endif // __glibcxx_copyable_function
+#ifdef __glibcxx_function_ref // C++ >= 26
+     friend class _Ref_base;
+#endif // __glibcxx_function_ref
    };
 #endif // __glibcxx_copyable_function || __glibcxx_copyable_function
 } // namespace __polyfunc
@@ -463,13 +493,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      protected:
        _Cpy_base() = default;
 
-       template<typename _Tp, typename... _Args>
+       template<auto _Invoke, typename _Tp, typename... _Args>
         void
         _M_init(_Args&&... __args)
         noexcept(_S_nothrow_init<_Tp, _Args...>())
         {
           _M_storage._M_init<_Tp>(std::forward<_Args>(__args)...);
-          _M_manage = _Manager::_S_select<true, _Tp>();
+          _M_manage = _Manager::_S_create<_Invoke, true, _Tp>();
         }
 
       void
@@ -530,6 +560,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            _M_ptrs._M_obj = __ptr;
        }
 
+#if __glibcxx_move_only_function || __glibcxx_copyable_function
+      // pre: _M_base is not empty
+      // Stores adress of object managed by __mo in _M_ptrs.
+      // Returns a type-erased pointer to invoker accepting _Ptrs.
+      auto
+      _M_adapt(_Mo_base const& __mo) noexcept
+       -> void(*)()
+      {
+        using _Op = _Manager::_Op;
+        _Storage __tmp;
+        __mo._M_manage(_Op::_Address, __tmp, &__mo._M_storage);
+        _M_ptrs = __tmp._M_ptrs;
+        __mo._M_manage(_Op::_PtrInvoke, __tmp, nullptr);
+        return __tmp._M_ptrs._M_func;
+      }
+#endif // _glibcxx_move_only_function || __glibcxx_copyable_function
+
       _Ptrs _M_ptrs;
     };
 
diff --git a/libstdc++-v3/include/bits/mofunc_impl.h 
b/libstdc++-v3/include/bits/mofunc_impl.h
index 017678d1d51..7abd7b95032 100644
--- a/libstdc++-v3/include/bits/mofunc_impl.h
+++ b/libstdc++-v3/include/bits/mofunc_impl.h
@@ -113,7 +113,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            }
 
          if constexpr (__is_polymorphic_function_v<_Vt>
-                         && __polyfunc::__is_invoker_convertible<_Vt, 
move_only_function>())
+              && __polyfunc::__is_invoker_convertible<_Vt, 
move_only_function>())
            {
              // Handle cases where _Fn is const reference to copyable_function,
              // by firstly creating temporary and moving from it.
@@ -122,10 +122,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
              _M_invoke = std::__exchange(__polyfunc::__invoker_of(__tmp), 
nullptr);
            }
          else
-           {
-             _M_init<_Vt>(std::forward<_Fn>(__f));
-             _M_invoke = _Invoker::template _S_storage<_Vt 
_GLIBCXX_MOF_INV_QUALS>();
-           }
+            _M_init<_Vt>(std::forward<_Fn>(__f));
        }
 
       /// Stores a target object initialized from the arguments.
@@ -135,7 +132,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        explicit
        move_only_function(in_place_type_t<_Tp>, _Args&&... __args)
        noexcept(_S_nothrow_init<_Tp, _Args...>())
-       : _M_invoke(_Invoker::template _S_storage<_Tp _GLIBCXX_MOF_INV_QUALS>())
        {
          static_assert(is_same_v<decay_t<_Tp>, _Tp>);
          _M_init<_Tp>(std::forward<_Args>(__args)...);
@@ -149,7 +145,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        move_only_function(in_place_type_t<_Tp>, initializer_list<_Up> __il,
                           _Args&&... __args)
        noexcept(_S_nothrow_init<_Tp, initializer_list<_Up>&, _Args...>())
-       : _M_invoke(_Invoker::template _S_storage<_Tp _GLIBCXX_MOF_INV_QUALS>())
        {
          static_assert(is_same_v<decay_t<_Tp>, _Tp>);
          _M_init<_Tp>(__il, std::forward<_Args>(__args)...);
@@ -229,6 +224,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       { return __x._M_invoke == nullptr; }
 
     private:
+      template<typename _Td, typename... _Args>
+       void
+       _M_init(_Args&&... __args)
+       noexcept(_S_nothrow_init<_Td, _Args...>())
+       {
+         using _Tr = _Td _GLIBCXX_MOF_INV_QUALS;
+         _Base::_M_init<_Invoker::template _S_ptrs<_Tr>(), _Td>(
+           std::forward<_Args>(__args)...);
+         _M_invoke = _Invoker::template _S_storage<_Tr>();
+       }
+
       typename _Invoker::__storage_func_t _M_invoke = nullptr;
 
       template<typename _Func>
diff --git a/libstdc++-v3/testsuite/20_util/function_ref/conv.cc 
b/libstdc++-v3/testsuite/20_util/function_ref/conv.cc
index d4bf15c03a9..5cfe7ec5de4 100644
--- a/libstdc++-v3/testsuite/20_util/function_ref/conv.cc
+++ b/libstdc++-v3/testsuite/20_util/function_ref/conv.cc
@@ -33,23 +33,23 @@ test01()
 
   // Complatible signatures
   std::function_ref<int(CountedArg) const noexcept> r2m(m1);
-  VERIFY( r2m(c) == 2 );
+  VERIFY( r2m(c) == 1 );
   std::function_ref<int(CountedArg) const noexcept> r2c(c1);
-  VERIFY( r2c(c) == 2 );
+  VERIFY( r2c(c) == 1 );
 
   std::function_ref<int(CountedArg) const> r3r(r1);
   VERIFY( r3r(c) == 1 );
   std::function_ref<int(CountedArg) const> r3m(m1);
-  VERIFY( r3m(c) == 2 );
+  VERIFY( r3m(c) == 1 );
   std::function_ref<int(CountedArg) const> r3c(c1);
-  VERIFY( r3c(c) == 2 );
+  VERIFY( r3c(c) == 1 );
 
   std::function_ref<int(CountedArg)> r4r(r1);
   VERIFY( r4r(c) == 1 );
   std::function_ref<int(CountedArg)> r4m(m1);
-  VERIFY( r4m(c) == 2 );
+  VERIFY( r4m(c) == 1 );
   std::function_ref<int(CountedArg)> r4c(c1);
-  VERIFY( r4c(c) == 2 );
+  VERIFY( r4c(c) == 1 );
 
   // Incompatible signatures
   std::function_ref<long(CountedArg) const noexcept> r5r(r1);
@@ -142,6 +142,54 @@ test05()
   VERIFY( f2(c) == 2 );
 }
 
+void
+test06()
+{
+  auto* func = +[]{ static int x; return &x; };
+  std::move_only_function<const void*() const> m1(func);
+  std::function_ref<const void*() const> rm1(m1);
+  VERIFY( m1() == rm1() );
+  std::copyable_function<const void*() const> c1(func);
+  std::function_ref<const void*() const> rc1(c1);
+  VERIFY( c1() == rc1() );
+
+  struct Trivial
+  {
+    void const* operator()() const
+    { return this; }
+  };
+  std::move_only_function<const void*() const> m2(Trivial{});
+  std::function_ref<const void*() const> rm2(m2);
+  VERIFY( m2() == rm2() );
+  std::copyable_function<const void*() const> c2(Trivial{});
+  std::function_ref<const void*() const> rc2(c2);
+  VERIFY( c2() == rc2() );
+
+  struct NonTrivial : Trivial
+  {
+    NonTrivial() {}
+    NonTrivial(NonTrivial&&) noexcept {}
+    NonTrivial(const NonTrivial&) {}
+  };
+  std::move_only_function<const void*() const> m3(NonTrivial{});
+  std::function_ref<const void*() const> rm3(m3);
+  VERIFY( m3() == rm3() );
+  std::copyable_function<const void*() const> c3(NonTrivial{});
+  std::function_ref<const void*() const> rc3(c3);
+  VERIFY( c3() == rc3() );
+
+  struct Large : Trivial
+  {
+    int tab[10];
+  };
+  std::move_only_function<const void*() const> m4(Large{});
+  std::function_ref<const void*() const> rm4(m4);
+  VERIFY( m4() == rm4() );
+  std::copyable_function<const void*() const> c4(Large{});
+  std::function_ref<const void*() const> rc4(c4);
+  VERIFY( c4() == rc4() );
+}
+
 constexpr bool
 test07()
 {
@@ -154,6 +202,7 @@ test07()
   return true;
 };
 
+
 int main()
 {
   test01();
@@ -161,6 +210,7 @@ int main()
   test03();
   test04();
   test05();
+  test06();
   test07();
 
   static_assert( test07() );
-- 
2.49.0

Reply via email to