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