This patch uses the additional provisions from LWG 4264, to avoid double indirection when function_ref is constructed from function_ref with compatible signature.
The details of compatible signatures follows the move_only_function as described in r16-617-g708d40ff109c6e49d02b684a368571722a160af8. However, due the non-owning nature of function_ref, it operator() is always const-quaulified, that means that we can constructor a function_ref<Ret(Args...) const> from function_ref<Ret(Args...)>, even if the underyling target will be mutated. 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. PR libstdc++/119126 libstdc++-v3/ChangeLog: * include/bits/cpyfunc_impl.h: (__polyfunc::__invoker_of) (__polyfunc::_base_of): Mark as constexpr. * include/bits/funcref_impl.h (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. (function_ref::function_ref): Init base class before _M_invoke consistently. * include/bits/funcwrap.h : (__polyfunc::__invoker_of) (__polyfunc::_base_of): Mark as constexpr. (__polyfunc::__is_function_ref_v, __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. Add constexpr tests. --- libstdc++-v3/include/bits/cpyfunc_impl.h | 4 +- libstdc++-v3/include/bits/funcref_impl.h | 48 ++++++++++++------- libstdc++-v3/include/bits/funcwrap.h | 26 +++++++++- libstdc++-v3/include/bits/mofunc_impl.h | 4 +- .../testsuite/20_util/function_ref/conv.cc | 27 ++++++++--- 5 files changed, 80 insertions(+), 29 deletions(-) diff --git a/libstdc++-v3/include/bits/cpyfunc_impl.h b/libstdc++-v3/include/bits/cpyfunc_impl.h index bc44cd3e313..7ba74ed8a8d 100644 --- a/libstdc++-v3/include/bits/cpyfunc_impl.h +++ b/libstdc++-v3/include/bits/cpyfunc_impl.h @@ -248,11 +248,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 1e19866035f..f3ec765b953 100644 --- a/libstdc++-v3/include/bits/funcref_impl.h +++ b/libstdc++-v3/include/bits/funcref_impl.h @@ -67,7 +67,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template<typename _Res, typename... _ArgTypes, bool _Noex> class function_ref<_Res(_ArgTypes...) _GLIBCXX_MOF_CV noexcept(_Noex)> + : __polyfunc::_Ref_base { + using _Base = __polyfunc::_Ref_base; using _Invoker = __polyfunc::_Invoker<_Noex, _Res, _ArgTypes...>; using _Signature = _Invoker::_Signature; @@ -85,8 +87,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. @@ -102,8 +104,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION constexpr 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_function_ref_v<_Vd> + && __polyfunc::__is_invoker_convertible<_Vd, 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 @@ -118,8 +131,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)). @@ -135,13 +148,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). @@ -157,8 +170,8 @@ _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> @@ -178,18 +191,19 @@ _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; + + template<typename _Dst, typename _Src> + friend consteval bool + __polyfunc::__is_invoker_convertible() noexcept; }; #undef _GLIBCXX_MOF_CV diff --git a/libstdc++-v3/include/bits/funcwrap.h b/libstdc++-v3/include/bits/funcwrap.h index cf261bcd4c8..1c1ba637689 100644 --- a/libstdc++-v3/include/bits/funcwrap.h +++ b/libstdc++-v3/include/bits/funcwrap.h @@ -205,12 +205,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); } @@ -518,6 +518,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; @@ -556,6 +571,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION -> function_ref< remove_pointer_t<decltype(__polyfunc::__deduce_funcref<_Fn, _Tp&>())>>; + /// @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; + /// @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 1ceb910e815..017678d1d51 100644 --- a/libstdc++-v3/include/bits/mofunc_impl.h +++ b/libstdc++-v3/include/bits/mofunc_impl.h @@ -232,11 +232,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 49ff0a78829..d4bf15c03a9 100644 --- a/libstdc++-v3/testsuite/20_util/function_ref/conv.cc +++ b/libstdc++-v3/testsuite/20_util/function_ref/conv.cc @@ -20,8 +20,8 @@ struct CountedArg }; CountedArg const c; -// The C++26 [func.wrap.general] p2 does not currently cover funciton_ref, -// so we make extra copies of arguments. +// LWG 4264 modify [func.wrap.general] p2 to also cover funciton_ref, +// so we make avoid extra copies in some situations. void test01() @@ -38,14 +38,14 @@ 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 ); + VERIFY( r4r(c) == 1 ); std::function_ref<int(CountedArg)> r4m(m1); VERIFY( r4m(c) == 2 ); std::function_ref<int(CountedArg)> r4c(c1); @@ -94,7 +94,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 ); @@ -103,7 +103,7 @@ test03() std::function_ref<int(CountedArg)> r3(f); VERIFY( r3(c) == 1 ); std::function_ref<int(CountedArg) const> r4(r3); - VERIFY( r4(c) == 2 ); + VERIFY( r4(c) == 1 ); std::move_only_function<int(CountedArg) const> m4(r3); VERIFY( m4(c) == 2 ); } @@ -142,6 +142,18 @@ test05() VERIFY( f2(c) == 2 ); } +constexpr bool +test07() +{ + auto f = [](int x) noexcept { return x; }; + std::function_ref<int(int) const noexcept> rf(f); + + std::function_ref<int(int) const noexcept> rr1(rf); + std::function_ref<int(int)> rr2(rf); + std::function_ref<int(long)> rr3(rf); + return true; +}; + int main() { test01(); @@ -149,4 +161,7 @@ int main() test03(); test04(); test05(); + test07(); + + static_assert( test07() ); } -- 2.49.0