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