[PATCH v2 1/3] libstdc++: Avoid double indirection in move_only_function when possible [PR119125]
Jonathan Wakely
jwakely@redhat.com
Wed May 14 10:52:21 GMT 2025
On 14/05/25 10:48 +0200, Tomasz Kamiński wrote:
>Based on the provision in C++26 [func.wrap.general] p2 this patch adjust the generic
>move_only_function(_Fn&&) constructor, such that when _Fn refers to selected
>move_only_function instantiations, the ownership of the target object is direclty
s/direclty/directly/
>transfered to constructor object. This avoid cost of double indireciton in this situation.
s/indireciton/indirection/
>We apply this also in C++23 mode.
>
>We also fix handling of self assigments, to match behavior required by standard,
s/assigments/assignments/
>due use of copy and swap idiom.
>
>An instantiations MF1 of move_only_function can transfer target of another
>instantiation MF2, if it can be constructed via usual rules (__is_callable_from<_MF2>),
>and their invoker are convertible (__is_invocer_convertible<MF2, MF1>()), i.e.:
>* MF1 is less noexcept than MF2,
>* return types are the same after stripping cv-quals
>* adujsted parameters type are the same (__poly::_param_t), i.e. param of types T and T&&
> are compatible for non-trivially copyable objects.
>Compatiblity of cv ref qualification is checked via __is_callable_from<_MF2>.
>
>To achieve above the generation of _M_invoke functions is moved to _Invoke class
s/_Invoke/_Invoker/
>templates, that only depends on noexcept, return type and adjusted parameter of the
>signature. To make the invoker signature compatible between const and mutable
>qualified signatures, we always accept _Storage as const& and perform a const_cast
>for locally stored object. This approach guarantees that we never strip const from
>const object.
>
>Another benefit of this approach is that move_only_function<void(std::string)>
>and move_only_function<void(std::string&&)> use same funciton pointer, which should
>reduce binary size.
>
>The _Storage and _Manager functionality was also extracted and adjusted from
>_Mo_func base, in preparation for implementation for copyable_function and
>function_ref. The _Storage was adjusted to store functions pointers as void(*)().
>The manage function, now accepts _Op enum parameter, and supports additional
>operations:
> * _Op::_Address stores address of target object in destination
> * _Op::_Copy, when enabled, copies from source to destination
>Furthremore, we provide a type-independent mamange functions for handling all:
s/Furthremore/Furthermore/
> * function pointer types
> * trivially copyable object stored locally.
>Similary as in case of invoker, we always pass source as const (for copy),
>and cast away constness in case of move operations, where we know that source
>is mutable.
>
>Finally, the new helpers are defined in __polyfunc internal namespace.
>
> PR libstdc++/119125
>
>libstdc++-v3/ChangeLog:
>
> * include/bits/mofunc_impl.h: (std::move_only_function): Adjusted for
> changes in bits/move_only_function.h
> (move_only_function::move_only_function(_Fn&&)): Special case
> move_only_functions with same invoker.
> (move_only_function::operator=(move_only_function&&)): Handle self
> assigment.
s/assigment/assignment/
> * include/bits/move_only_function.h (__polyfunc::_Ptrs)
> (__polyfunc::_Storage): Refactored from _Mo_func::_Storage.
> (__polyfunc::__param_t): Moved from move_only_function::__param_t.
> (__polyfunc::_Base_invoker, __polyfunc::_Invoke): Refactored from
s/_Invoke/_Invoker/
> move_only_function::_S_invoke.
> (__polyfunc::_Manager): Refactored from _Mo_func::_S_manager.
> (std::_Mofunc_base): Moved into __polyfunc::_Mo_base with parts
> extracted to __polyfunc::_Storage and __polyfunc::_Manager.
> (__polyfunc::__deref_as, __polyfunc::__invoker_of)
> (__polyfunc::__base_of, __polyfunc::__is_invoker_convertible): Define.
> (std::__is_move_only_function_v): Renamed to
> __is_polymorphic_function_v.
> (std::__is_polymorphic_function_v): Renamed from
> __is_move_only_function_v.
> * testsuite/20_util/move_only_function/call.cc: Test for
> functions pointers.
> * testsuite/20_util/move_only_function/conv.cc: New test.
> * testsuite/20_util/move_only_function/move.cc: Tests for
> self assigment.
>---
>In addition to adjusting formatting and fixing typo, this update:
> * consistently call global new when placement new is used, and
> non-global for heap allocations
> * moves _Invoker before _Manager.
>The _Invoker can be supported for non hosted enviroment, as well
>as function_ref.
>
> libstdc++-v3/include/bits/mofunc_impl.h | 74 +--
> .../include/bits/move_only_function.h | 455 +++++++++++++-----
> .../20_util/move_only_function/call.cc | 14 +
> .../20_util/move_only_function/conv.cc | 188 ++++++++
> .../20_util/move_only_function/move.cc | 11 +
> 5 files changed, 588 insertions(+), 154 deletions(-)
> create mode 100644 libstdc++-v3/testsuite/20_util/move_only_function/conv.cc
>
>diff --git a/libstdc++-v3/include/bits/mofunc_impl.h b/libstdc++-v3/include/bits/mofunc_impl.h
>index 318a55e618f..5eb4b5a0047 100644
>--- a/libstdc++-v3/include/bits/mofunc_impl.h
>+++ b/libstdc++-v3/include/bits/mofunc_impl.h
>@@ -62,8 +62,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<typename _Res, typename... _ArgTypes, bool _Noex>
> class move_only_function<_Res(_ArgTypes...) _GLIBCXX_MOF_CV
> _GLIBCXX_MOF_REF noexcept(_Noex)>
>- : _Mofunc_base
>+ : __polyfunc::_Mo_base
> {
>+ using _Base = __polyfunc::_Mo_base;
>+ using _Invoker = __polyfunc::_Invoker<_Noex, _Res, _ArgTypes...>;
>+ using _Signature = _Invoker::_Signature;
>+
> template<typename _Tp>
> using __callable
> = __conditional_t<_Noex,
>@@ -87,7 +91,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> /// Moves the target object, leaving the source empty.
> move_only_function(move_only_function&& __x) noexcept
>- : _Mofunc_base(static_cast<_Mofunc_base&&>(__x)),
>+ : _Base(static_cast<_Base&&>(__x)),
> _M_invoke(std::__exchange(__x._M_invoke, nullptr))
> { }
>
>@@ -99,13 +103,25 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> {
> if constexpr (is_function_v<remove_pointer_t<_Vt>>
> || is_member_pointer_v<_Vt>
>- || __is_move_only_function_v<_Vt>)
>+ || __is_polymorphic_function_v<_Vt>)
> {
> if (__f == nullptr)
> return;
> }
>- _M_init<_Vt>(std::forward<_Fn>(__f));
>- _M_invoke = &_S_invoke<_Vt>;
>+ if constexpr (__is_polymorphic_function_v<_Vt>
>+ && __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.
>+ _Vt __tmp(std::forward<_Fn>(__f));
>+ _M_move(__polyfunc::__base_of(__tmp));
>+ _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>();
>+ }
> }
>
> /// Stores a target object initialized from the arguments.
>@@ -115,7 +131,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> explicit
> move_only_function(in_place_type_t<_Tp>, _Args&&... __args)
> noexcept(_S_nothrow_init<_Tp, _Args...>())
>- : _M_invoke(&_S_invoke<_Tp>)
>+ : _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)...);
>@@ -129,7 +145,7 @@ _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(&_S_invoke<_Tp>)
>+ : _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)...);
>@@ -139,8 +155,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> move_only_function&
> operator=(move_only_function&& __x) noexcept
> {
>- _Mofunc_base::operator=(static_cast<_Mofunc_base&&>(__x));
>- _M_invoke = std::__exchange(__x._M_invoke, nullptr);
>+ // Standard requires support of self assigment, by specifying it as
s/assigment/assignment/
>+ // copy and swap.
>+ if (this != addressof(__x)) [[likely]]
Qualify as std::addressof
>+ {
>+ _Base::operator=(static_cast<_Base&&>(__x));
>+ _M_invoke = std::__exchange(__x._M_invoke, nullptr);
>+ }
> return *this;
> }
>
>@@ -148,7 +169,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> move_only_function&
> operator=(nullptr_t) noexcept
> {
>- _Mofunc_base::operator=(nullptr);
>+ _M_reset();
> _M_invoke = nullptr;
> return *this;
> }
>@@ -167,7 +188,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> ~move_only_function() = default;
>
> /// True if a target object is present, false otherwise.
>- explicit operator bool() const noexcept { return _M_invoke != nullptr; }
>+ explicit operator bool() const noexcept
>+ { return _M_invoke != nullptr; }
>
> /** Invoke the target object.
> *
>@@ -181,14 +203,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> operator()(_ArgTypes... __args) _GLIBCXX_MOF_CV_REF noexcept(_Noex)
> {
> __glibcxx_assert(*this != nullptr);
>- return _M_invoke(this, std::forward<_ArgTypes>(__args)...);
>+ return _M_invoke(this->_M_storage, std::forward<_ArgTypes>(__args)...);
> }
>
> /// Exchange the target objects (if any).
> void
> swap(move_only_function& __x) noexcept
> {
>- _Mofunc_base::swap(__x);
>+ _Base::swap(__x);
> std::swap(_M_invoke, __x._M_invoke);
> }
>
>@@ -203,25 +225,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> { return __x._M_invoke == nullptr; }
>
> private:
>- template<typename _Tp>
>- using __param_t = __conditional_t<is_scalar_v<_Tp>, _Tp, _Tp&&>;
>+ typename _Invoker::__storage_func_t _M_invoke = nullptr;
>
>- using _Invoker = _Res (*)(_Mofunc_base _GLIBCXX_MOF_CV*,
>- __param_t<_ArgTypes>...) noexcept(_Noex);
>+ template<typename _Func>
>+ friend auto&
>+ __polyfunc::__invoker_of(_Func&) noexcept;
>
>- template<typename _Tp>
>- static _Res
>- _S_invoke(_Mofunc_base _GLIBCXX_MOF_CV* __self,
>- __param_t<_ArgTypes>... __args) noexcept(_Noex)
>- {
>- using _TpCv = _Tp _GLIBCXX_MOF_CV;
>- using _TpInv = _Tp _GLIBCXX_MOF_INV_QUALS;
>- return std::__invoke_r<_Res>(
>- std::forward<_TpInv>(*_S_access<_TpCv>(__self)),
>- std::forward<__param_t<_ArgTypes>>(__args)...);
>- }
>+ template<typename _Func>
>+ friend auto&
>+ __polyfunc::__base_of(_Func&) noexcept;
>
>- _Invoker _M_invoke = nullptr;
>+ template<typename _Dst, typename _Src>
>+ friend consteval bool
>+ __polyfunc::__is_invoker_convertible() noexcept;
> };
>
> #undef _GLIBCXX_MOF_CV_REF
>diff --git a/libstdc++-v3/include/bits/move_only_function.h b/libstdc++-v3/include/bits/move_only_function.h
>index 42b33d01901..305fe986818 100644
>--- a/libstdc++-v3/include/bits/move_only_function.h
>+++ b/libstdc++-v3/include/bits/move_only_function.h
>@@ -45,145 +45,349 @@ namespace std _GLIBCXX_VISIBILITY(default)
> {
> _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
>- template<typename... _Signature>
>- class move_only_function; // not defined
>-
> /// @cond undocumented
>- class _Mofunc_base
>- {
>- protected:
>- _Mofunc_base() noexcept
>- : _M_manage(_S_empty)
>- { }
>-
>- _Mofunc_base(_Mofunc_base&& __x) noexcept
>- {
>- _M_manage = std::__exchange(__x._M_manage, _S_empty);
>- _M_manage(_M_storage, &__x._M_storage);
>- }
>-
>- template<typename _Tp, typename... _Args>
>- static constexpr bool
>- _S_nothrow_init() noexcept
>- {
>- if constexpr (__stored_locally<_Tp>)
>- return is_nothrow_constructible_v<_Tp, _Args...>;
>- return false;
>- }
>-
>- template<typename _Tp, typename... _Args>
>- void
>- _M_init(_Args&&... __args) noexcept(_S_nothrow_init<_Tp, _Args...>())
>- {
>- if constexpr (__stored_locally<_Tp>)
>- ::new (_M_storage._M_addr()) _Tp(std::forward<_Args>(__args)...);
>- else
>- _M_storage._M_p = new _Tp(std::forward<_Args>(__args)...);
>-
>- _M_manage = &_S_manage<_Tp>;
>- }
>-
>- _Mofunc_base&
>- operator=(_Mofunc_base&& __x) noexcept
>- {
>- _M_manage(_M_storage, nullptr);
>- _M_manage = std::__exchange(__x._M_manage, _S_empty);
>- _M_manage(_M_storage, &__x._M_storage);
>- return *this;
>- }
>-
>- _Mofunc_base&
>- operator=(nullptr_t) noexcept
>- {
>- _M_manage(_M_storage, nullptr);
>- _M_manage = _S_empty;
>- return *this;
>- }
>-
>- ~_Mofunc_base() { _M_manage(_M_storage, nullptr); }
>-
>- void
>- swap(_Mofunc_base& __x) noexcept
>- {
>- // Order of operations here is more efficient if __x is empty.
>- _Storage __s;
>- __x._M_manage(__s, &__x._M_storage);
>- _M_manage(__x._M_storage, &_M_storage);
>- __x._M_manage(_M_storage, &__s);
>- std::swap(_M_manage, __x._M_manage);
>- }
>-
>- template<typename _Tp, typename _Self>
>- static _Tp*
>- _S_access(_Self* __self) noexcept
>- {
>- if constexpr (__stored_locally<remove_const_t<_Tp>>)
>- return static_cast<_Tp*>(__self->_M_storage._M_addr());
>- else
>- return static_cast<_Tp*>(__self->_M_storage._M_p);
>- }
>+ template<typename _Tp>
>+ inline constexpr bool __is_polymorphic_function_v = false;
>
>- private:
>- struct _Storage
>+ namespace __polyfunc
>+ {
>+ union _Ptrs
> {
>- void* _M_addr() noexcept { return &_M_bytes[0]; }
>- const void* _M_addr() const noexcept { return &_M_bytes[0]; }
>-
>- // We want to have enough space to store a simple delegate type.
>- struct _Delegate { void (_Storage::*__pfm)(); _Storage* __obj; };
>- union {
>- void* _M_p;
>- alignas(_Delegate) alignas(void(*)())
>- unsigned char _M_bytes[sizeof(_Delegate)];
>- };
>+ void* _M_obj;
>+ void (*_M_func)();
> };
>
>- template<typename _Tp>
>- static constexpr bool __stored_locally
>- = sizeof(_Tp) <= sizeof(_Storage) && alignof(_Tp) <= alignof(_Storage)
>- && is_nothrow_move_constructible_v<_Tp>;
>-
>- // A function that either destroys the target object stored in __target,
>- // or moves the target object from *__src to __target.
>- using _Manager = void (*)(_Storage& __target, _Storage* __src) noexcept;
>+ struct _Storage
>+ {
>+ void* _M_addr() noexcept { return &_M_bytes[0]; }
>+ void const* _M_addr() const noexcept { return &_M_bytes[0]; }
>+
>+ template<typename _Tp>
>+ static consteval bool
>+ _S_stored_locally() noexcept
>+ {
>+ return sizeof(_Tp) <= sizeof(_Storage)
>+ && alignof(_Tp) <= alignof(_Storage)
>+ && is_nothrow_move_constructible_v<_Tp>;
>+ }
>+
>+ template<typename _Tp, typename... _Args>
>+ static consteval bool
>+ _S_nothrow_init() noexcept
>+ {
>+ if constexpr (_S_stored_locally<_Tp>())
>+ return is_nothrow_constructible_v<_Tp, _Args...>;
>+ return false;
>+ }
>+
>+ template<typename _Tp, typename... _Args>
>+ void
>+ _M_init(_Args&&... __args) noexcept(_S_nothrow_init<_Tp, _Args...>())
>+ {
>+ if constexpr (is_function_v<remove_pointer_t<_Tp>>)
>+ {
>+ static_assert( sizeof...(__args) <= 1 );
>+ // __args can have up to one element, returns nullptr if empty.
>+ _Tp __func = (nullptr, ..., __args);
>+ _M_ptrs._M_func = reinterpret_cast<void(*)()>(__func);
>+ }
>+ else if constexpr (!_S_stored_locally<_Tp>())
>+ _M_ptrs._M_obj = new _Tp(std::forward<_Args>(__args)...);
>+ else
>+ ::new (_M_addr()) _Tp(std::forward<_Args>(__args)...);
>+ }
>+
>+ template<typename _Tp>
>+ [[__gnu__::__always_inline__]]
>+ _Tp*
>+ _M_ptr() const noexcept
>+ {
>+ if constexpr (!_S_stored_locally<remove_const_t<_Tp>>())
>+ return static_cast<_Tp*>(_M_ptrs._M_obj);
>+ else if constexpr (is_const_v<_Tp>)
>+ return static_cast<_Tp*>(_M_addr());
>+ else
>+ // _Manager and _Invoker pass _Storage by const&, even for mutable sources.
>+ return static_cast<_Tp*>(const_cast<void*>(_M_addr()));
>+ }
>+
>+ template<typename _Ref>
>+ [[__gnu__::__always_inline__]]
>+ _Ref
>+ _M_ref() const noexcept
>+ {
>+ using _Tp = remove_reference_t<_Ref>;
>+ if constexpr (is_function_v<remove_pointer_t<_Tp>>)
>+ return reinterpret_cast<_Tp>(_M_ptrs._M_func);
>+ else
>+ return static_cast<_Ref>(*_M_ptr<_Tp>());
>+ }
>+
>+ // We want to have enough space to store a simple delegate type.
>+ struct _Delegate { void (_Storage::*__pfm)(); _Storage* __obj; };
>+ union {
>+ _Ptrs _M_ptrs;
>+ alignas(_Delegate) alignas(void(*)())
>+ unsigned char _M_bytes[sizeof(_Delegate)];
>+ };
>+ };
>+
>+ template<bool _Noex, typename _Ret, typename... _Args>
>+ struct _Base_invoker
>+ {
>+ using _Signature = _Ret(*)(_Args...) noexcept(_Noex);
>+
>+ using __storage_func_t = _Ret(*)(const _Storage&, _Args...) noexcept(_Noex);
>+ template<typename _Tp>
>+ static consteval __storage_func_t
>+ _S_storage()
>+ { return &_S_call_storage<_Adjust_target<_Tp>>; }
>+
>+ private:
>+ template<typename _Tp, typename _Td = remove_cvref_t<_Tp>>
>+ using _Adjust_target =
>+ __conditional_t<is_pointer_v<_Td> || is_member_pointer_v<_Td>, _Td, _Tp>;
>+
>+ template<typename _Tp>
>+ static _Ret
>+ _S_call_storage(const _Storage& __ref, _Args... __args) noexcept(_Noex)
>+ {
>+ return std::__invoke_r<_Ret>(__ref._M_ref<_Tp>(),
>+ std::forward<_Args>(__args)...);
>+ }
>+ };
>+
>+ template<typename _Tp>
>+ using __param_t = __conditional_t<is_scalar_v<_Tp>, _Tp, _Tp&&>;
>+
>+ template<bool _Noex, typename _Ret, typename... _Args>
>+ using _Invoker = _Base_invoker<_Noex, remove_cv_t<_Ret>, __param_t<_Args>...>;
>+
>+ template<typename _Func>
>+ auto&
>+ __invoker_of(_Func& __f) noexcept
>+ { return __f._M_invoke; }
>+
>+ template<typename _Func>
>+ auto&
>+ __base_of(_Func& __f) noexcept
>+ { return static_cast<__like_t<_Func&, typename _Func::_Base>>(__f); }
>+
>+ template<typename _Src, typename _Dst>
>+ consteval bool
>+ __is_invoker_convertible() noexcept
>+ {
>+ if constexpr (requires { typename _Src::_Signature; })
>+ return is_convertible_v<typename _Src::_Signature,
>+ typename _Dst::_Signature>;
>+ else
>+ return false;
>+ }
>+
>+ struct _Manager
>+ {
>+ enum class _Op
>+ {
>+ // saves address of entity in *__src to __target._M_ptrs,
>+ _Address,
>+ // moves entity stored in *__src to __target, __src becomes empty
>+ _Move,
>+ // copies entity stored in *__src to __target, supported only if
>+ // _ProvideCopy is specified.
>+ _Copy,
>+ // destroys entity stored in __target, __src is ignoring
>+ _Destroy,
>+ };
>+
>+ // A function that performs operation __op on the __target and possibly __src.
>+ using _Func = void (*)(_Op __op, _Storage& __target, const _Storage* __src) noexcept;
>
> // The no-op manager function for objects with no target.
>- static void _S_empty(_Storage&, _Storage*) noexcept { }
>+ static void _S_empty(_Op, _Storage&, const _Storage*) noexcept { }
>
>- // The real manager function for a target object of type _Tp.
>- template<typename _Tp>
>- static void
>- _S_manage(_Storage& __target, _Storage* __src) noexcept
>+ template<bool _ProvideCopy, typename _Tp>
>+ consteval static auto
>+ _S_select()
> {
>- if constexpr (__stored_locally<_Tp>)
>- {
>- if (__src)
>- {
>- _Tp* __rval = static_cast<_Tp*>(__src->_M_addr());
>- ::new (__target._M_addr()) _Tp(std::move(*__rval));
>- __rval->~_Tp();
>- }
>- else
>- static_cast<_Tp*>(__target._M_addr())->~_Tp();
>- }
>+ if constexpr (is_function_v<remove_pointer_t<_Tp>>)
>+ return &_S_func;
>+ else if constexpr (!_Storage::_S_stored_locally<_Tp>())
>+ return &_S_ptr<_ProvideCopy, _Tp>;
>+ else if constexpr (is_trivially_copyable_v<_Tp>)
>+ return &_S_trivial;
> else
>- {
>- if (__src)
>- __target._M_p = __src->_M_p;
>- else
>- delete static_cast<_Tp*>(__target._M_p);
>- }
>+ return &_S_local<_ProvideCopy, _Tp>;
> }
>
>- _Storage _M_storage;
>- _Manager _M_manage;
>- };
>+ private:
>+ static void
>+ _S_func(_Op __op, _Storage& __target, const _Storage* __src) noexcept
>+ {
>+ switch (__op)
>+ {
>+ case _Op::_Address:
>+ case _Op::_Move:
>+ case _Op::_Copy:
>+ __target._M_ptrs._M_func = __src->_M_ptrs._M_func;
>+ return;
>+ case _Op::_Destroy:
>+ return;
>+ }
>+ }
>+
>+ static void
>+ _S_trivial(_Op __op, _Storage& __target, const _Storage* __src) noexcept
>+ {
>+ switch (__op)
>+ {
>+ case _Op::_Address:
>+ __target._M_ptrs._M_obj = const_cast<void*>(__src->_M_addr());
>+ return;
>+ case _Op::_Move:
>+ case _Op::_Copy:
>+ // N.B. Creating _Storage starts lifetime of _M_bytes char array,
>+ // that implicitly creates, amongst other, are possibly trivially
The word "are" here is confusing me, should it be "any" or "all"? Or
just removed?
>+ // copyable objects, so we copy any object present in __src._M_bytes.
>+ ::new (&__target) _Storage(*__src);
>+ return;
>+ case _Op::_Destroy:
>+ return;
>+ }
>+ }
>+
>+ template<bool _Provide_copy, typename _Tp>
>+ static void
>+ _S_local(_Op __op, _Storage& __target, const _Storage* __src)
>+ noexcept(!_Provide_copy)
>+ {
>+ switch (__op)
>+ {
>+ case _Op::_Address:
>+ __target._M_ptrs._M_obj = __src->_M_ptr<_Tp>();
>+ return;
>+ case _Op::_Move:
>+ {
>+ _Tp* __obj = __src->_M_ptr<_Tp>();
>+ ::new(__target._M_addr()) _Tp(std::move(*__obj));
>+ __obj->~_Tp();
>+ }
>+ return;
>+ case _Op::_Destroy:
>+ __target._M_ptr<_Tp>()->~_Tp();
>+ return;
>+ case _Op::_Copy:
>+ if constexpr (_Provide_copy)
>+ ::new (__target._M_addr()) _Tp(__src->_M_ref<const _Tp&>());
>+ else
>+ __builtin_unreachable();
>+ return;
>+ }
>+ }
>+
>+ template<bool _Provide_copy, typename _Tp>
>+ static void
>+ _S_ptr(_Op __op, _Storage& __target, const _Storage* __src)
>+ noexcept(!_Provide_copy)
>+ {
>+ switch (__op)
>+ {
>+ case _Op::_Address:
>+ case _Op::_Move:
>+ __target._M_ptrs._M_obj = __src->_M_ptrs._M_obj;
>+ return;
>+ case _Op::_Destroy:
>+ delete __target._M_ptr<_Tp>();
>+ return;
>+ case _Op::_Copy:
>+ if constexpr (_Provide_copy)
>+ __target._M_ptrs._M_obj = new _Tp(__src->_M_ref<const _Tp&>());
>+ else
>+ __builtin_unreachable();
>+ return;
>+ }
>+ }
>+ };
>+
>+ class _Mo_base
>+ {
>+ protected:
>+ _Mo_base() noexcept
>+ : _M_manage(_Manager::_S_empty)
>+ { }
>+
>+ _Mo_base(_Mo_base&& __x) noexcept
>+ { _M_move(__x); }
>+
>+ template<typename _Tp, typename... _Args>
>+ static consteval bool
>+ _S_nothrow_init() noexcept
>+ { return _Storage::_S_nothrow_init<_Tp, _Args...>(); }
>+
>+ template<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>();
>+ }
>+
>+ void
>+ _M_move(_Mo_base& __x) noexcept
>+ {
>+ using _Op = _Manager::_Op;
>+ _M_manage = std::__exchange(__x._M_manage, _Manager::_S_empty);
>+ _M_manage(_Op::_Move, _M_storage, &__x._M_storage);
>+ }
>+
>+ _Mo_base&
>+ operator=(_Mo_base&& __x) noexcept
>+ {
>+ _M_destroy();
>+ _M_move(__x);
>+ return *this;
>+ }
>+
>+ void
>+ _M_reset() noexcept
>+ {
>+ _M_destroy();
>+ _M_manage = _Manager::_S_empty;
>+ }
>+
>+ ~_Mo_base()
>+ { _M_destroy(); }
>+
>+ void
>+ swap(_Mo_base& __x) noexcept
>+ {
>+ using _Op = _Manager::_Op;
>+ // Order of operations here is more efficient if __x is empty.
>+ _Storage __s;
>+ __x._M_manage(_Op::_Move, __s, &__x._M_storage);
>+ _M_manage(_Op::_Move, __x._M_storage, &_M_storage);
>+ __x._M_manage(_Op::_Move, _M_storage, &__s);
>+ std::swap(_M_manage, __x._M_manage);
>+ }
>+
>+ _Storage _M_storage;
>+
>+ private:
>+ void _M_destroy() noexcept
>+ { _M_manage(_Manager::_Op::_Destroy, _M_storage, nullptr); }
>+
>+ _Manager::_Func _M_manage;
>+ };
>+
>+} // namespace __polyfunc
>+ /// @endcond
>
>+ template<typename... _Signature>
>+ class move_only_function; // not defined
>+
>+ /// @cond undocumented
> template<typename _Tp>
>- inline constexpr bool __is_move_only_function_v = false;
>- template<typename _Tp>
>- constexpr bool __is_move_only_function_v<move_only_function<_Tp>> = true;
>- /// @endcond
>+ constexpr bool __is_polymorphic_function_v<move_only_function<_Tp>> = true;
>
> namespace __detail::__variant
> {
>@@ -196,6 +400,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> : true_type
> { };
> } // namespace __detail::__variant
>+ /// @endcond
>
> _GLIBCXX_END_NAMESPACE_VERSION
> } // namespace std
>diff --git a/libstdc++-v3/testsuite/20_util/move_only_function/call.cc b/libstdc++-v3/testsuite/20_util/move_only_function/call.cc
>index bfc609afe37..217de374763 100644
>--- a/libstdc++-v3/testsuite/20_util/move_only_function/call.cc
>+++ b/libstdc++-v3/testsuite/20_util/move_only_function/call.cc
>@@ -190,6 +190,19 @@ test04()
> VERIFY( std::move(std::as_const(f5))() == 3 );
> }
>
>+void
>+test05()
>+{
>+ int (*fp)() = [] { return 0; };
>+ move_only_function<int()> f0{fp};
>+ VERIFY( f0() == 0 );
>+ VERIFY( std::move(f0)() == 0 );
>+
>+ const move_only_function<int() const> f1{fp};
>+ VERIFY( f1() == 0 );
>+ VERIFY( std::move(f1)() == 0 );
>+}
>+
> struct Incomplete;
>
> void
>@@ -206,5 +219,6 @@ int main()
> test02();
> test03();
> test04();
>+ test05();
> test_params();
> }
>diff --git a/libstdc++-v3/testsuite/20_util/move_only_function/conv.cc b/libstdc++-v3/testsuite/20_util/move_only_function/conv.cc
>new file mode 100644
>index 00000000000..3da5e9e90a3
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/20_util/move_only_function/conv.cc
>@@ -0,0 +1,188 @@
>+// { dg-do run { target c++23 } }
>+// { dg-require-effective-target hosted }
>+
>+#include <functional>
>+#include <testsuite_hooks.h>
>+
>+using std::move_only_function;
>+
>+static_assert( !std::is_constructible_v<std::move_only_function<void()>,
>+ std::move_only_function<void()&>> );
>+static_assert( !std::is_constructible_v<std::move_only_function<void()>,
>+ std::move_only_function<void()&&>> );
>+static_assert( !std::is_constructible_v<std::move_only_function<void()&>,
>+ std::move_only_function<void()&&>> );
>+static_assert( !std::is_constructible_v<std::move_only_function<void() const>,
>+ std::move_only_function<void()>> );
>+
>+// Non-trivial args, guarantess that type is not passed by copy
>+struct CountedArg
>+{
>+ CountedArg() = default;
>+ CountedArg(const CountedArg& f) noexcept : counter(f.counter) { ++counter; }
>+ CountedArg& operator=(CountedArg&&) = delete;
>+
>+ int counter = 0;
>+};
>+CountedArg const c;
>+
>+// When move_only_functions is constructed from other move_only_function,
>+// the compiler can avoid double indirection per C++26 [func.wrap.general] p2.
>+
>+void
>+test01()
>+{
>+ auto f = [](CountedArg const& arg) noexcept { return arg.counter; };
>+ std::move_only_function<int(CountedArg) const noexcept> m1(f);
>+ VERIFY( m1(c) == 1 );
>+
>+ std::move_only_function<int(CountedArg) const> m2(std::move(m1));
>+ VERIFY( m2(c) == 1 );
>+
>+ std::move_only_function<int(CountedArg)> m3(std::move(m2));
>+ VERIFY( m3(c) == 1 );
>+
>+ // Invokers internally uses Counted&& for non-trivial types,
>+ // sinature remain compatible.
>+ std::move_only_function<int(CountedArg&&)> m4(std::move(m3));
>+ VERIFY( m4({}) == 0 );
>+
>+ std::move_only_function<int(CountedArg&&)&&> m5(std::move(m4));
>+ VERIFY( std::move(m5)({}) == 0 );
>+
>+ m4 = f;
>+ std::move_only_function<int(CountedArg&&)&> m7(std::move(m4));
>+ VERIFY( m7({}) == 0 );
>+
>+ m4 = f;
>+ std::move_only_function<int(CountedArg&&)&> m8(std::move(m4));
>+ VERIFY( m8({}) == 0 );
>+
>+ // Incompatible signatures
>+ m1 = f;
>+ std::move_only_function<long(CountedArg) const noexcept> m9(std::move(m1));
>+ VERIFY( m9(c) == 2 );
>+}
>+
>+void
>+test02()
>+{
>+ auto f = [](CountedArg const& arg) noexcept { return arg.counter; };
>+ std::move_only_function<int(CountedArg) const noexcept> m1(f);
>+ VERIFY( m1(c) == 1 );
>+
>+ std::move_only_function<int(CountedArg) const> m2;
>+ m2 = std::move(m1);
>+ VERIFY( m2(c) == 1 );
>+
>+ std::move_only_function<int(CountedArg)> m3;
>+ m3 = std::move(m2);
>+ VERIFY( m3(c) == 1 );
>+
>+ // Invokers internally uses Counted&& for non-trivial types,
>+ // sinature remain compatible.
>+ std::move_only_function<int(CountedArg&&)> m4;
>+ m4 = std::move(m3);
>+ VERIFY( m4({}) == 0 );
>+
>+ std::move_only_function<int(CountedArg&&)&&> m5;
>+ m5 = std::move(m4);
>+ VERIFY( std::move(m5)({}) == 0 );
>+
>+ m4 = f;
>+ std::move_only_function<int(CountedArg&&)&> m7;
>+ m7 = std::move(m4);
>+ VERIFY( m7({}) == 0 );
>+
>+ m4 = f;
>+ std::move_only_function<int(CountedArg&&)&> m8;
>+ m8 = std::move(m4);
>+ VERIFY( m8({}) == 0 );
>+
>+ m1 = f;
>+ std::move_only_function<long(CountedArg) const noexcept> m9;
>+ m9 = std::move(m1);
>+ VERIFY( m9(c) == 2 );
>+}
>+
>+void
>+test03()
>+{
>+ std::move_only_function<int(long) const noexcept> e;
>+ VERIFY( e == nullptr );
>+
>+ std::move_only_function<int(long) const> e2(std::move(e));
>+ VERIFY( e2 == nullptr );
>+ e2 = std::move(e);
>+ VERIFY( e2 == nullptr );
>+
>+ std::move_only_function<bool(int) const> e3(std::move(e));
>+ VERIFY( e3 == nullptr );
>+ e3 = std::move(e);
>+ VERIFY( e3 == nullptr );
>+}
>+
>+void
>+test04()
>+{
>+ struct F
>+ {
>+ int operator()(CountedArg const& arg) noexcept
>+ { return arg.counter; }
>+
>+ int operator()(CountedArg const& arg) const noexcept
>+ { return arg.counter + 1000; }
>+ };
>+
>+ F f;
>+ std::move_only_function<int(CountedArg) const> m1(f);
>+ VERIFY( m1(c) == 1001 );
>+
>+ // Call const overload as std::move_only_function<int(CountedArg) const>
>+ // inside std::move_only_function<int(CountedArg)> would do.
>+ std::move_only_function<int(CountedArg)> m2(std::move(m1));
>+ VERIFY( m2(c) == 1001 );
>+
>+ std::move_only_function<int(CountedArg)> m3(f);
>+ VERIFY( m3(c) == 1 );
>+}
>+
>+void
>+test05()
>+{
>+ auto f = [](CountedArg const& arg) noexcept { return arg.counter; };
>+ std::move_only_function<int(CountedArg)> w1(f);
>+ // move_only_function stores move_only_function due incompatibile signatures
>+ std::move_only_function<int(CountedArg const&)> w2(std::move(w1));
>+ // copy is made when passing to int(CountedArg)
>+ VERIFY( w2(c) == 1 );
>+ // wrapped 3 times
>+ w1 = std::move(w2);
>+ VERIFY( w1(c) == 2 );
>+ // wrapped 4 times
>+ w2 = std::move(w1);
>+ VERIFY( w2(c) == 2 );
>+ // wrapped 5 times
>+ w1 = std::move(w2);
>+ VERIFY( w1(c) == 3 );
>+}
>+
>+void
>+test06()
>+{
>+ // No special interoperability with std::function
>+ auto f = [](CountedArg const& arg) noexcept { return arg.counter; };
>+ std::function<int(CountedArg)> f1(f);
>+ std::move_only_function<int(CountedArg) const> m1(std::move(f1));
>+ VERIFY( m1(c) == 2 );
>+}
>+
>+int main()
>+{
>+ test01();
>+ test02();
>+ test03();
>+ test04();
>+ test05();
>+ test06();
>+}
>diff --git a/libstdc++-v3/testsuite/20_util/move_only_function/move.cc b/libstdc++-v3/testsuite/20_util/move_only_function/move.cc
>index 51e31a6323d..6da02c9cd81 100644
>--- a/libstdc++-v3/testsuite/20_util/move_only_function/move.cc
>+++ b/libstdc++-v3/testsuite/20_util/move_only_function/move.cc
>@@ -32,6 +32,12 @@ test01()
> VERIFY( m1().copy == 1 );
> VERIFY( m1().move == 0 );
>
>+ // Standard specifies move assigment as copy and swap
>+ m1 = std::move(m1);
>+ VERIFY( m1 != nullptr );
>+ VERIFY( m1().copy == 1 );
>+ VERIFY( m1().move == 0 );
>+
> // This will move construct a new target object and destroy the old one:
> auto m2 = std::move(m1);
> VERIFY( m1 == nullptr && m2 != nullptr );
>@@ -80,6 +86,11 @@ test02()
> VERIFY( m1().copy == 1 );
> VERIFY( m1().move == 0 );
>
>+ m1 = std::move(m1);
>+ VERIFY( m1 != nullptr );
>+ VERIFY( m1().copy == 1 );
>+ VERIFY( m1().move == 0 );
>+
> // The target object is on the heap so this just moves a pointer:
> auto m2 = std::move(m1);
> VERIFY( m1 == nullptr && m2 != nullptr );
>--
>2.49.0
>
>
More information about the Libstdc++
mailing list