On Wed, May 14, 2025 at 12:52 PM Jonathan Wakely <jwak...@redhat.com> wrote:
> 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? > I have changed it to "all" ending up with: // N.B. Creating _Storage starts lifetime of _M_bytes char array, // that implicitly creates, amongst other, all possible trivially // copyable objects, so we copy any object present in __src._M_bytes. > >+ // 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 > > > > > >