On Mon, May 12, 2025 at 12:57 PM Tomasz Kamiński <tkami...@redhat.com> 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 > transfered to constructor object. This avoid cost of double indireciton in > this situation. > We apply this also in C++23 mode. > > We also fix handling of self assigments, to match behavior required by > standard, > 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 > 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: > * 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: Added __glibcxx_move_only_function > guard. > (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. > * include/bits/move_only_function.h (__polyfunc::_Ptrs) > (__polyfunc::_Storage): Refactored from _Mo_func::_Storage. > (__polyfunc::_Manager): Refactored from _Mo_func::_S_manager. > (__polyfunc::__param_t): Moved from move_only_function::__param_t. > (__polyfunc::_Base_invoker, __polyfunc::_Invoke): Refactored from > move_only_function::_S_invoke. > (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_polymorphic_function_v. > * testsuite/20_util/move_only_function/conv.cc: New test. > * testsuite/20_util/move_only_function/move.cc: Tests for self > assigment. > --- > libstdc++-v3/include/bits/mofunc_impl.h | 75 +-- > .../include/bits/move_only_function.h | 449 +++++++++++++----- > .../20_util/move_only_function/conv.cc | 188 ++++++++ > .../20_util/move_only_function/move.cc | 11 + > 4 files changed, 569 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..839f19e0389 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,26 @@ _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 creatiogn temporary and moving from it. This > may move > + // locally stored object twice. > + _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 +132,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 +146,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 +156,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 > + // copy and swap. > + if (this != addressof(__x)) [[likely]] > + { > + _Base::operator=(static_cast<_Base&&>(__x)); > + _M_invoke = std::__exchange(__x._M_invoke, nullptr); > + } > return *this; > } > > @@ -148,7 +170,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 +189,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 +204,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 +226,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..58d6f2ee29e 100644 > --- a/libstdc++-v3/include/bits/move_only_function.h > +++ b/libstdc++-v3/include/bits/move_only_function.h > @@ -45,145 +45,343 @@ 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> > + 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> > + _Tp* _M_ptr() const noexcept > + { > + if constexpr (is_function_v<remove_cv_t<_Tp>>) > + return reinterpret_cast<_Tp*>(_M_ptrs._M_func); > + else 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> > + _Ref _M_ref() const noexcept > + { > + using _Tp = remove_reference_t<_Ref>; > + return static_cast<_Ref>(*_M_ptr<_Tp>()); > This will try to read function pointer from _M_obj, instead of _M_func. I need to add test for this. > + } > + > + // 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)]; > + }; > + }; > + > + 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. _Storage starts lifetime of _M_bytes char array, > + // that implicitly creates, amongst other, are possibly > trivially > + // copyable objects, so we copy any present in __src. > + 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; > + } > + } > + }; > + > + 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) > + { > + // N.B. static_cast<_Args> in contrast to forward<_Args> does > not bind > + // reference if argument is passed by value. > + return std::__invoke_r<_Ret>(__ref._M_ref<_Tp>(), > + static_cast<_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>...>; > + > + 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; > + }; > + > + 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; > + } > +} // 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 +394,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/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 > >