On Wed, May 14, 2025 at 9:41 AM Tomasz Kaminski <tkami...@redhat.com> wrote:

>
>
> On Tue, May 13, 2025 at 11:16 PM Patrick Palka <ppa...@redhat.com> wrote:
>
>> On Mon, 12 May 2025, 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
>> > 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/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.
>> > ---
>> >  libstdc++-v3/include/bits/mofunc_impl.h       |  75 +--
>> >  .../include/bits/move_only_function.h         | 454 +++++++++++++-----
>> >  .../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..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
>>
>> typo, 'creating'
>>
>> > +           // 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&
>>
>> Should be indented two spaces more than template-head
>>
>> > +       __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;
>>
>> Spaces/tab issue on this line
>>
>> >
>> > -      _Invoker _M_invoke = nullptr;
>> > +      template<typename _Dst, typename _Src>
>> > +        friend consteval bool
>>
>> Should be a tab instead of spaces
>>
>> > +     __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..5eb688a0ef4 100644
>> > --- a/libstdc++-v3/include/bits/move_only_function.h
>> > +++ b/libstdc++-v3/include/bits/move_only_function.h
>> > @@ -45,145 +45,348 @@ 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)...);
>>
>> Why is global operator new/delete used in some call sites but not
>> others?
>>
> It should always be global new, fixing it.
>
I have checked, and previously we were using a global placement, when using
storage,
that was paired with explicit invocaiton of destructor. However, for heap
allocating new
we were using non-global new and matching delete. I think I will preserve
that, there is
no reason to non-use user provided new when allocating on heap.


>
>> > -     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;
>>
>> This should be 'inline' I think, otherwise there might be 'std' module
>> issues?
>>
> Isn't them being a variable template sufficient?
> I have added inline, because __is_move_only_function was inline.
>
>>
>> >
>> > -  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)];
>> > +     };
>> > +   };
>> > +
>> > +   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
>>
>> Missing newline before function name
>>
>> > +     {
>> > +       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
>>
>> Same
>>
>> LGTM otherwise
>>
>> > +     {
>> > +       _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 +399,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
>> >
>> >
>
>

Reply via email to