From 1a60e0357ce528afedfe0b361382b58d11296466 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 11 Jun 2025 19:57:30 -0700 Subject: [PATCH] add move_only_function --- include/boost/compat/move_only_function.hpp | 1972 +++++++++++++++++++ include/boost/compat/type_traits.hpp | 11 +- test/Jamfile | 2 + test/move_only_function_test.cpp | 855 ++++++++ 4 files changed, 2839 insertions(+), 1 deletion(-) create mode 100644 include/boost/compat/move_only_function.hpp create mode 100644 test/move_only_function_test.cpp diff --git a/include/boost/compat/move_only_function.hpp b/include/boost/compat/move_only_function.hpp new file mode 100644 index 0000000..7669444 --- /dev/null +++ b/include/boost/compat/move_only_function.hpp @@ -0,0 +1,1972 @@ +#ifndef BOOST_COMPAT_MOVE_ONLY_FUNCTION_HPP_INCLUDED +#define BOOST_COMPAT_MOVE_ONLY_FUNCTION_HPP_INCLUDED + +// Copyright 2025 Christian Mazakas. +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include + +#include +#include +#include + +#include + +#if BOOST_WORKAROUND(BOOST_GCC, >= 6 * 10000) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnonnull-compare" +# pragma GCC diagnostic ignored "-Waddress" +#endif + +namespace boost { +namespace compat { + +template +class move_only_function; + +template< class T > +struct in_place_type_t { explicit in_place_type_t() = default; }; + +namespace detail +{ + +union pointers +{ + void* pobj_; + void ( *pfn_ )(); +}; + +struct storage +{ + // we want SBO to be large enough to store a type which can be used for delegation purposes + struct delegate { void( storage::*pmfn_ )(); storage* pobj_; }; + + union + { + void* pobj_; + void ( *pfn_ )(); + alignas(delegate) unsigned char buf_[ sizeof(delegate) ]; + }; + + template + constexpr static bool use_sbo() noexcept + { + return sizeof( T ) <= sizeof( storage ) && alignof( T ) <= alignof( storage ) && std::is_nothrow_move_constructible::value; + } + + void* addr() noexcept + { + return buf_; + } +}; + +template +struct is_polymorphic_function : std::false_type +{ +}; + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +#if defined(__cpp_noexcept_function_type) + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +template +struct is_polymorphic_function> : std::true_type +{ +}; + +#endif + +template +using is_move_only_function = is_polymorphic_function; + +template +struct is_in_place_type_t : std::false_type +{ +}; + +template +struct is_in_place_type_t> : std::true_type +{ +}; + +template +struct nothrow_init +{ + constexpr static bool const value = ( storage::use_sbo() && std::is_nothrow_constructible::value ) || false; +}; + +enum class ref_quals { none, lvalue, rvalue }; + +template +struct is_callable_from; + +template +struct is_callable_from +{ + using cv_VT = conditional_t, VT>; + + using cv_ref_VT = conditional_t< + RQ == ref_quals::none, cv_VT, + conditional_t< + RQ == ref_quals::rvalue, add_rvalue_reference_t, add_lvalue_reference_t + > + >; + + using inv_quals_VT = conditional_t< + RQ == ref_quals::none, add_lvalue_reference_t, + conditional_t< + RQ == ref_quals::rvalue, add_rvalue_reference_t, add_lvalue_reference_t + > + >; + + constexpr static bool const value = + is_nothrow_invocable_r::value && + is_nothrow_invocable_r::value; +}; + +template +struct is_callable_from +{ + using cv_VT = conditional_t, VT>; + + using cv_ref_VT = conditional_t< + RQ == ref_quals::none, cv_VT, + conditional_t< + RQ == ref_quals::rvalue, add_rvalue_reference_t, add_lvalue_reference_t + > + >; + + using inv_quals_VT = conditional_t< + RQ == ref_quals::none, add_lvalue_reference_t, + conditional_t< + RQ == ref_quals::rvalue, add_rvalue_reference_t, add_lvalue_reference_t + > + >; + + constexpr static bool const value = + is_invocable_r::value && + is_invocable_r::value; +}; + +inline std::nullptr_t get_first_arg() +{ + return nullptr; +} + +template +T&& get_first_arg( T&& t, CArgs&& ... ) +{ + return std::forward( t ); +} + +template +bool is_nullary_arg( Ts&&... ) +{ + return false; +} + +template< + class F, class VT = decay_t, + enable_if_t< + std::is_member_pointer::value || + is_move_only_function::value, + int> = 0 +> +bool is_nullary_arg( F&& f ) +{ + return f == nullptr; +} + +template< + class F, class VT = decay_t, + enable_if_t< + std::is_function>::value, + int> = 0 +> +bool is_nullary_arg( F f ) +{ + return f == nullptr; +} + +template +struct mo_invoke_function_holder +{ + static R invoke_function( storage s, Args&&... args) noexcept( NoEx ) + { + auto f = reinterpret_cast( s.pfn_ ); + return compat::invoke_r( f, std::forward( args )... ); + } +}; + +template +struct mo_invoke_object_holder +{ + static R invoke_object( storage s, Args&&... args ) noexcept( NoEx ) + { + using T = remove_reference_t; + using cv_T = conditional_t, T>; + using cv_ref_T = conditional_t< + RQ == ref_quals::none, add_lvalue_reference_t, + conditional_t< + RQ == ref_quals::rvalue, add_rvalue_reference_t, add_lvalue_reference_t + > + >; + return compat::invoke_r( static_cast( *static_cast( s.pobj_ ) ), std::forward( args )... ); + } +}; + +template +struct mo_invoke_local_holder +{ + static R invoke_local( storage s, Args&&... args ) noexcept( NoEx ) + { + using T = remove_reference_t; + using cv_T = conditional_t, T>; + using cv_ref_T = conditional_t< + RQ == ref_quals::none, add_lvalue_reference_t, + conditional_t< + RQ == ref_quals::rvalue, add_rvalue_reference_t, add_lvalue_reference_t + > + >; + + return compat::invoke_r( static_cast( *static_cast( s.addr() ) ), std::forward( args )... ); + } +}; + +enum class op_type { move, destroy }; + +template +struct move_only_function_base +{ + move_only_function_base() = default; + + move_only_function_base( move_only_function_base&& rhs ) noexcept + { + manager_ = rhs.manager_; + manager_( op_type::move, s_, &rhs.s_ ); + + invoke_ = rhs.invoke_; + rhs.invoke_ = nullptr; + rhs.manager_ = &manage_empty; + } + + ~move_only_function_base() + { + destroy(); + } + + void swap( move_only_function_base& rhs ) noexcept + { + // to properly swap with storages, we need to treat the destination storage + // the same as the source storage, which means that we need to use the + // source manager_'s move operation + + storage s; + rhs.manager_( op_type::move, s, &rhs.s_ ); + manager_( op_type::move, rhs.s_, &s_ ); + rhs.manager_( op_type::move, s_, &s ); + + std::swap( manager_, rhs.manager_ ); + std::swap( invoke_, rhs.invoke_ ); + } + + move_only_function_base& operator=( move_only_function_base&& rhs ) + { + destroy(); + + manager_ = rhs.manager_; + manager_( op_type::move, s_, &rhs.s_ ); + invoke_ = rhs.invoke_; + + rhs.invoke_ = nullptr; + rhs.manager_ = &manage_empty; + return *this; + } + + move_only_function_base& operator=( std::nullptr_t ) noexcept + { + destroy(); + invoke_ = nullptr; + manager_ = &manage_empty; + return *this; + } + + static void manage_empty( op_type, detail::storage&, detail::storage* ) + { + } + + static void manage_function( op_type op, detail::storage& s, detail::storage* src ) + { + switch( op ) + { + case op_type::move: + s.pfn_ = src->pfn_; + src->pfn_ = nullptr; + break; + + default: + break; + } + + } + + template + static void manage_object( op_type op, detail::storage& s, detail::storage* src ) + { + switch( op ) + { + case op_type::destroy: + delete static_cast( s.pobj_ ); + break; + + case op_type::move: + s.pobj_ = src->pobj_; + src->pobj_ = nullptr; + break; + + default: + break; + } + } + + template + static void manage_local( op_type op, detail::storage& s, detail::storage* src ) + { + switch( op ) + { + case op_type::destroy: + static_cast( s.addr() )->~VT(); + break; + + case op_type::move: + { + VT* p = static_cast( src->addr() ); + new(s.addr()) VT( std::move( *p ) ); + // destruct the element here because move construction will leave the container empty + // outside of this function + p->~VT(); + break; + } + + default: + break; + } + } + + template + void + move_from_compatible_base( move_only_function_base& base ) + { + using polymorphic_base = move_only_function_base; + + manager_ = base.manager_; + + manager_( op_type::move, s_, &base.s_ ); + invoke_ = base.invoke_; + + base.invoke_ = nullptr; + base.manager_ = &polymorphic_base::manage_empty; + } + + template + void base_init( std::true_type, F&& f ) + { + move_from_compatible_base( f ); + } + + template + void base_init( std::false_type, F&&... ) + { + } + + template + void init( std::false_type /* is_function */, CArgs&& ...args ) + { + if( is_polymorphic_function::value ) + { + base_init( is_polymorphic_function{}, std::forward( args )... ); + return; + } + + if( !storage::use_sbo() ) + { + s_.pobj_ = new VT( std::forward( args )... ); + invoke_ = &mo_invoke_object_holder::invoke_object; + manager_ = &manage_object; + } + else + { + new( s_.addr() ) VT( std::forward( args )... ); + invoke_ = &mo_invoke_local_holder::invoke_local; + manager_ = &manage_local; + } + } + + template + void init( std::true_type /* is_function */, CArgs ...args ) + { + R (*pfn)( Args... ) = get_first_arg( args... ); + s_.pfn_ = reinterpret_cast( pfn ); + invoke_ = &detail::mo_invoke_function_holder::invoke_function; + manager_ = &manage_function; + } + + template + void init( type_identity, CArgs&& ...args ) + { + init( std::is_function>(), std::forward( args )... ); + } + + void destroy() + { + manager_( op_type::destroy, s_, nullptr ); + } + + explicit operator bool() const noexcept + { + return invoke_ != nullptr; + } + + detail::storage s_; +#if defined(__cpp_noexcept_function_type) + R ( *invoke_ )( detail::storage, Args&&... ) noexcept( NoEx ) = nullptr; +#else + R ( *invoke_ )( detail::storage, Args&&... ) = nullptr; +#endif + void ( *manager_ )( op_type, detail::storage&, detail::storage* ) = &manage_empty; +}; + +} // namespace detail + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) & + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) && + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) const + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) const & + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) const && + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +#if defined(__cpp_noexcept_function_type) + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) noexcept + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) & noexcept + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) && noexcept + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) const noexcept + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) const& noexcept + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +template +class move_only_function : detail::move_only_function_base +{ +private: + + template + friend struct detail::move_only_function_base; + + using base = detail::move_only_function_base; + +public: + + move_only_function() noexcept + { + } + + move_only_function( std::nullptr_t ) noexcept + : move_only_function() + { + } + + template< + class F, + class VT = decay_t, + enable_if_t< + !std::is_same>::value && + !detail::is_in_place_type_t::value && + detail::is_callable_from::value, + int> = 0 + > + move_only_function( F&& f ) noexcept( detail::nothrow_init::value ) + { + if( detail::is_nullary_arg( std::forward( f ) ) ) return; + base::init( type_identity{}, std::forward( f ) ); + } + + template< + class T, class ...CArgs, + enable_if_t< + std::is_constructible::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, CArgs&& ... args ) noexcept( detail::nothrow_init::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, std::forward( args )... ); + } + + template< + class T, class U, class ...CArgs, + enable_if_t< + std::is_constructible&, CArgs...>::value && + detail::is_callable_from::value, + int> = 0 + > + explicit move_only_function( in_place_type_t, std::initializer_list il, CArgs&& ... args ) noexcept( detail::nothrow_init&, CArgs...>::value ) + { + static_assert( std::is_same>::value, "T and `decay_t` must be the same" ); + base::init( type_identity{}, il, std::forward( args )... ); + } + + move_only_function( move_only_function const& ) = delete; + move_only_function( move_only_function&& ) = default; + + ~move_only_function() = default; + + move_only_function& operator=( move_only_function&& rhs ) + { + if( this != &rhs ) + { + this->base::operator=( static_cast( rhs ) ); + } + return *this; + } + + move_only_function& operator=( std::nullptr_t ) noexcept + { + this->base::operator=( nullptr ); + return *this; + } + + template move_only_function& operator=( F&& f ) + { + move_only_function( std::forward( f ) ).swap( *this ); + return *this; + } + + friend bool operator==( move_only_function const& fn, std::nullptr_t ) noexcept + { + return fn.invoke_ == nullptr; + } + + friend bool operator!=( move_only_function const& fn, std::nullptr_t ) noexcept + { + return !( fn == nullptr ); + } + + void swap( move_only_function& rhs ) noexcept + { + if( this != &rhs ) + { + this->base::swap( rhs ); + } + } + + friend void swap( move_only_function& lhs, move_only_function& rhs ) noexcept + { + lhs.swap( rhs ); + } + + explicit operator bool() const noexcept + { + return static_cast( *static_cast( this ) ); + } + + R operator()( Args... args ) const&& noexcept + { + return this->invoke_( this->s_, std::forward( args )... ); + } +}; + +#endif + +} // namespace compat +} // namespace boost + +#if BOOST_WORKAROUND(BOOST_GCC, >= 6 * 10000) +# pragma GCC diagnostic pop +#endif + +#endif // #ifndef BOOST_COMPAT_MOVE_ONLY_FUNCTION_HPP_INCLUDED diff --git a/include/boost/compat/type_traits.hpp b/include/boost/compat/type_traits.hpp index b9a1f10..0868d8e 100644 --- a/include/boost/compat/type_traits.hpp +++ b/include/boost/compat/type_traits.hpp @@ -2,7 +2,7 @@ #define BOOST_COMPAT_TYPE_TRAITS_HPP_INCLUDED // Copyright 2024 Peter Dimov -// Copyright 2024 Christian Mazakas +// Copyright 2024-2025 Christian Mazakas // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt @@ -11,10 +11,14 @@ namespace boost { namespace compat { +template using add_lvalue_reference_t = typename std::add_lvalue_reference::type; +template using add_rvalue_reference_t = typename std::add_rvalue_reference::type; + template using remove_const_t = typename std::remove_const::type; template using remove_cv_t = typename std::remove_cv::type; template using remove_reference_t = typename std::remove_reference::type; template using remove_cvref_t = remove_cv_t< remove_reference_t >; +template using remove_pointer_t = typename std::remove_pointer::type; template using decay_t = typename std::decay::type; @@ -35,6 +39,11 @@ template using void_t = typename detail::make_void::type; template using add_const_t = typename std::add_const::type; +template struct type_identity +{ + using type = T; +}; + } // namespace compat } // namespace boost diff --git a/test/Jamfile b/test/Jamfile index 991ce61..8989b32 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -113,5 +113,7 @@ run function_ref_fn_noexcept_test.cpp ; run function_ref_mfn_noexcept_test.cpp ; run function_ref_obj_noexcept_test.cpp ; +run move_only_function_test.cpp ; + run to_array_lvalue_test.cpp ; run to_array_rvalue_test.cpp ; diff --git a/test/move_only_function_test.cpp b/test/move_only_function_test.cpp new file mode 100644 index 0000000..00917ca --- /dev/null +++ b/test/move_only_function_test.cpp @@ -0,0 +1,855 @@ +#include +#include + +#if BOOST_WORKAROUND(BOOST_GCC, >= 7 * 10000 && BOOST_GCC < 8 * 10000) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnoexcept-type" +#endif + +#include + +#include + +#include +#include + +#include +#include +#include + +#if BOOST_WORKAROUND(BOOST_GCC, >= 13 * 10000) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wself-move" +#elif defined(BOOST_CLANG) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wself-move" +#endif + +using std::is_same; +using std::is_constructible; +using std::is_nothrow_constructible; + +using boost::compat::move_only_function; +using boost::compat::in_place_type_t; +// using std::move_only_function; +// using std::in_place_type_t; +using boost::compat::invoke_result_t; +using boost::compat::is_invocable; +using boost::compat::is_nothrow_invocable; + +template +using add_const_t = typename std::add_const::type; + +template +constexpr add_const_t& as_const_ref( T& t ) noexcept +{ + return t; +} + +void func0() {} + +int func1() +{ + return 1; +} + +int func2( int x ) +{ + return x + 1; +} + +int func3( std::unique_ptr x ) +{ + return *x + 1; +} + +template +std::unique_ptr make_unique( Args&&... args ) +{ + return std::unique_ptr( new T( std::forward( args )... ) ); +} + +struct callable +{ + std::unique_ptr p_ = make_unique( 1 ); + + callable() = default; + + callable( std::unique_ptr p ): p_( std::move( p ) ) + { + } + + callable( std::initializer_list il, int x, int y, int z ) + { + int sum = 0; + for( auto const v : il ) + { + sum += v; + } + sum += x; + sum += y; + sum += z; + p_ = make_unique( sum ); + } + + callable( callable&& rhs ) noexcept + { + p_ = make_unique( *rhs.p_ ); + } + + int operator()( int x ) + { + return *p_ + x; + } +}; + +struct noex_callable +{ + std::unique_ptr p_ = make_unique( 1 ); + + noex_callable() = default; + noex_callable( noex_callable&& rhs ) noexcept + { + p_ = make_unique( *rhs.p_ ); + } + + noex_callable( noex_callable const& ) + { + } + + int operator()( int x ) noexcept + { + return *p_ + x; + } +}; + +struct large_callable +{ + unsigned char padding[ 256 ] = {}; + std::unique_ptr p_ = make_unique( 1 ); + + large_callable() = default; + + large_callable( std::unique_ptr p ): p_( std::move( p ) ) + { + } + + large_callable( large_callable&& rhs ) noexcept + { + p_ = make_unique( *rhs.p_ ); + } + + int operator()( int x ) + { + return *p_ + x; + } + + int operator()( int x, int y ) + { + return x + y; + } +}; + +struct person +{ + std::string name_; + int age_ = -1; + std::unique_ptr p_ = make_unique( 1 ); + + int age() + { + return age_; + } +}; + +static void test_call() +{ + { + move_only_function f1; + BOOST_TEST( !f1 ); + + move_only_function f2; + BOOST_TEST( !f2 ); + } + + { + move_only_function f1( nullptr ); + BOOST_TEST( !f1 ); + + move_only_function f2( nullptr ); + BOOST_TEST( !f2 ); + } + + { + move_only_function f1( func0 ); + f1(); + BOOST_TEST( f1 ); + + move_only_function f2( func0 ); + f2(); + BOOST_TEST( f2 ); + } + + { + int ( *fp )() = [] { return 0; }; + + move_only_function f1( fp ); + BOOST_TEST( f1 ); + BOOST_TEST_EQ( f1(), 0 ); + BOOST_TEST_EQ( std::move( f1() ), 0 ); + + move_only_function f2( fp ); + BOOST_TEST( f2 ); + BOOST_TEST_EQ( f2(), 0 ); + BOOST_TEST_EQ( std::move( f2() ), 0 ); + + fp = nullptr; + move_only_function f3( fp ); + BOOST_TEST_NOT( f3 ); + } + + { + struct declared; + enum class integer : int; + + move_only_function f1; + move_only_function f2; + move_only_function f3; + move_only_function f4; + } + + { + move_only_function f1( func1 ); + BOOST_TEST( f1 ); + BOOST_TEST_EQ( f1(), 1 ); + BOOST_TEST_EQ( std::move( f1() ), 1 ); + + move_only_function f2( func1 ); + BOOST_TEST( f2 ); + BOOST_TEST_EQ( f2(), 1 ); + BOOST_TEST_EQ( std::move( f2() ), 1 ); + } + + { + move_only_function )> fn( func3 ); + BOOST_TEST_EQ( fn( make_unique( 1 ) ), 2 ); + BOOST_TEST_EQ( std::move( fn )( make_unique( 1 ) ), 2 ); + + auto x = make_unique( 1 ); + BOOST_TEST( fn ); + BOOST_TEST_EQ( fn( std::move( x ) ), 2 ); + } + + { + auto l = []() { return 1; }; + + move_only_function f1( l ); + BOOST_TEST( f1 ); + BOOST_TEST_EQ( f1(), 1 ); + BOOST_TEST_EQ( std::move( f1 )(), 1 ); + + move_only_function f2( l ); + BOOST_TEST( f2 ); + BOOST_TEST_EQ( f2(), 1 ); + BOOST_TEST_EQ( as_const_ref( f2 )(), 1 ); + BOOST_TEST_EQ( std::move( f2 )(), 1 ); + BOOST_TEST_EQ( std::move( as_const_ref( f2 ) )(), 1 ); + } + + { + move_only_function f1( []() { return 1; } ); + BOOST_TEST( f1 ); + BOOST_TEST_EQ( f1(), 1 ); + BOOST_TEST_EQ( std::move( f1 )(), 1 ); + + move_only_function f2( []() { return 1; } ); + BOOST_TEST( f2 ); + BOOST_TEST_EQ( f2(), 1 ); + BOOST_TEST_EQ( as_const_ref( f2 )(), 1 ); + BOOST_TEST_EQ( std::move( f2 )(), 1 ); + BOOST_TEST_EQ( std::move( as_const_ref( f2 ) )(), 1 ); + } + + { + auto l = []( int x ) { return x + 1; }; + + move_only_function f1( l ); + BOOST_TEST( f1 ); + BOOST_TEST_EQ( f1( 1 ), 2 ); + + move_only_function f2( l ); + BOOST_TEST( f2 ); + BOOST_TEST_EQ( f2( 1 ), 2 ); + } + + { + auto l = []( std::unique_ptr x ) { return *x + 1; }; + move_only_function )> f( std::move( l ) ); + BOOST_TEST( f ); + BOOST_TEST_EQ( f( make_unique( 1 ) ), 2 ); + } + + { + auto p = make_unique( 1 ); + auto l = []( std::unique_ptr& x ) { return *x + 1; }; + move_only_function& )> f( l ); + BOOST_TEST( f ); + BOOST_TEST_EQ( f( p ), 2 ); + } + + { + int x = 1; + move_only_function f1( callable{} ); + BOOST_TEST( f1 ); + BOOST_TEST_EQ( f1( 1 ), 2); + BOOST_TEST_EQ( f1( x ), 2 ); + + int y = 2; + callable c; + move_only_function f2( std::move( c ) ); + BOOST_TEST( f2 ); + BOOST_TEST_EQ( f2( 2 ), 3 ); + BOOST_TEST_EQ( f2( y ), 3 ); + } + + { + move_only_function f1( large_callable{} ); + BOOST_TEST( f1 ); + BOOST_TEST_EQ( f1( 1, 2 ), 3); + + large_callable c; + move_only_function f2( std::move( c ) ); + BOOST_TEST( f2 ); + BOOST_TEST_EQ( f2( 1, 2 ), 3 ); + } + + { + move_only_function f1( in_place_type_t{}, make_unique( 4321 ) ); + BOOST_TEST_EQ( f1( 1234 ), 5555 ); + + move_only_function f2( in_place_type_t{}, make_unique( 4321 ) ); + BOOST_TEST_EQ( f2( 1234 ), 5555 ); + + move_only_function f3( in_place_type_t{}, func2 ); + BOOST_TEST_EQ( f3( 1233 ), 1234 ); + + move_only_function f4( in_place_type_t{} ); + BOOST_TEST( f4 != nullptr ); + + move_only_function f5( in_place_type_t{}, std::initializer_list{ 1, 2, 3 }, 4, 5, 6 ); + BOOST_TEST_EQ( f5( 7 ), 1 + 2 + 3 + 4 + 5 + 6 + 7 ); + } + + { + move_only_function f1( func1 ); + move_only_function f2( std::move( f1 ) ); + + BOOST_TEST( !f1 ); + BOOST_TEST( f2 ); + BOOST_TEST_EQ( f2(), 1 ); + } + + { + move_only_function f1( callable{} ); + move_only_function f2( std::move( f1 ) ); + + BOOST_TEST( !f1 ); + BOOST_TEST( f2 ); + BOOST_TEST_EQ( f2( 2 ), 3 ); + } + + { + move_only_function f1( large_callable{} ); + move_only_function f2( std::move( f1 ) ); + + BOOST_TEST( !f1 ); + BOOST_TEST( f2 ); + BOOST_TEST_EQ( f2( 1, 2 ), 3 ); + } + + { + person p; + p.age_ = 35; + + move_only_function f1( &person::age_ ); + BOOST_TEST_EQ( f1( p ), 35 ); + + p.age_ = 53; + + move_only_function f2( &person::age ); + BOOST_TEST_EQ( f2( p ), 53 ); + + int person::*mp = nullptr; + move_only_function f3( mp ); + BOOST_TEST_NOT( f3 ); + + int (person::*mfp)() = nullptr; + move_only_function f4( mfp ); + BOOST_TEST_NOT( f4 ); + } + + { + struct tester + { + int operator()() + { + return 1; + } + + int operator()() const + { + return 2; + } + }; + + tester t; + + move_only_function f1( t ); + move_only_function f2( t ); + + BOOST_TEST_EQ( f1(), 1 ); + BOOST_TEST_EQ( std::move( f1 )(), 1 ); + BOOST_TEST_EQ( f2(), 2 ); + BOOST_TEST_EQ( as_const_ref( f2 )(), 2 ); + BOOST_TEST_EQ( std::move( as_const_ref( f2 ) )(), 2 ); + } + + { + struct tester + { + int operator()() noexcept + { + return 1; + } + + int operator()() const noexcept + { + return 2; + } + }; + + tester t; + + move_only_function f1( t ); + move_only_function f2( t ); + + BOOST_TEST_EQ( f1(), 1 ); + BOOST_TEST_EQ( std::move( f1 )(), 1 ); + BOOST_TEST_EQ( f2(), 2 ); + BOOST_TEST_EQ( as_const_ref( f2 )(), 2 ); + BOOST_TEST_EQ( std::move( as_const_ref( f2 ) )(), 2 ); + } + + { + struct tester + { + int operator()() & + { + return 1; + } + + int operator()() && + { + return 2; + } + + int operator()() const& + { + return 3; + } + + int operator()() const&& + { + return 4; + } + }; + + tester t; + + move_only_function f1( t ); + move_only_function f2( t ); + move_only_function f3( t ); + + BOOST_TEST_EQ( f1(), 1 ); + BOOST_TEST_EQ( std::move( f2 )(), 2 ); + BOOST_TEST_EQ( f3(), 3 ); + BOOST_TEST_EQ( as_const_ref( f3 )(), 3 ); + +#if !BOOST_WORKAROUND(BOOST_GCC, < 40900) + + move_only_function f4( t ); + BOOST_TEST_EQ( std::move( f4 )(), 4 ); + BOOST_TEST_EQ( std::move( as_const_ref( f4 ) )(), 4 ); + +#endif + } + + { + struct tester + { + int operator()() & noexcept + { + return 1; + } + + int operator()() && noexcept + { + return 2; + } + + int operator()() const& noexcept + { + return 3; + } + + int operator()() const&& noexcept + { + return 4; + } + }; + + tester t; + + move_only_function f1( t ); + move_only_function f2( t ); + move_only_function f3( t ); + + BOOST_TEST_EQ( f1(), 1 ); + BOOST_TEST_EQ( std::move( f2 )(), 2 ); + BOOST_TEST_EQ( f3(), 3 ); + BOOST_TEST_EQ( as_const_ref( f3 )(), 3 ); + +#if !BOOST_WORKAROUND(BOOST_GCC, < 40900) + + move_only_function f4( t ); + BOOST_TEST_EQ( std::move( f4 )(), 4 ); + BOOST_TEST_EQ( std::move( as_const_ref( f4 ) )(), 4 ); + +#endif + } + + { + move_only_function f1( callable{} ); + // f1 = std::move( f1 ); + // BOOST_TEST_EQ( f1( 1233 ), 1234 ); + + move_only_function f2( large_callable{} ); + f2 = std::move( f1 ); + BOOST_TEST_EQ( f2( 1233 ), 1234 ); + BOOST_TEST_NOT( f1 ); + + move_only_function f3( callable{} ); + move_only_function f4( large_callable{} ); + f3 = std::move( f4 ); + BOOST_TEST_EQ( f3( 1233 ), 1234 ); + BOOST_TEST_NOT( f4 ); + + move_only_function f5( callable{} ); + f5 = nullptr; + BOOST_TEST_NOT( f5 ); + + move_only_function f6( large_callable{} ); + f6 = nullptr; + BOOST_TEST_NOT( f6 ); + + move_only_function f7; + f7 = nullptr; + BOOST_TEST_NOT( f7 ); + + move_only_function f8( callable{} ); + f8 = large_callable{}; + BOOST_TEST_EQ( f8( 1233 ), 1234 ); + + move_only_function f9( large_callable{} ); + f9 = callable{}; + BOOST_TEST_EQ( f9( 1233 ), 1234 ); + + move_only_function f10; + f10 = callable{}; + BOOST_TEST_EQ( f10( 1233 ), 1234 ); + + move_only_function f11; + f11 = large_callable{}; + BOOST_TEST_EQ( f11( 1233 ), 1234 ); + } + + { + callable c1; + *c1.p_ = 1234; + + large_callable c2; + *c2.p_ = 4321; + + move_only_function f1( std::move( c1 ) ); + move_only_function f2( std::move( c2 ) ); + + int x = 1; + + BOOST_TEST_EQ( f1( x ), 1235 ); + BOOST_TEST_EQ( f2( x ), 4322 ); + + swap( f1, f2 ); + + BOOST_TEST_EQ( f1( x ), 4322 ); + BOOST_TEST_EQ( f2( x ), 1235 ); + + move_only_function f3( callable{} ); + move_only_function f4; + + swap( f3, f4 ); + BOOST_TEST_NOT( f3 ); + BOOST_TEST( f4 ); + + move_only_function f5; + move_only_function f6; + + swap( f5, f6 ); + BOOST_TEST_NOT( f5 ); + BOOST_TEST_NOT( f6 ); + } + + { + struct throwing + { + static int x() + { + throw 1234; + } + + int operator()() + { + throw 1234; + } + }; + + struct large_throwing + { + char padding[ 256 ] = {}; + + int operator()() + { + throw 1234; + } + }; + + move_only_function f1( &throwing::x ); + move_only_function f2( throwing{} ); + move_only_function f3( large_throwing{} ); + + BOOST_TEST_THROWS( f1(), int ); + BOOST_TEST_THROWS( f2(), int ); + BOOST_TEST_THROWS( f3(), int ); + } +} + +struct Q +{ + void operator()() const &; + void operator()() &&; +}; + +struct R +{ + void operator()() &; + void operator()() &&; +}; + +// These types are all small and nothrow move constructible +struct F { void operator()(); }; +struct G { void operator()() const; }; + +struct H +{ + H( int ); + H( int, int ) noexcept; + void operator()() noexcept; +}; + +struct I +{ + I( int, const char* ); + I( std::initializer_list ); + int operator()() const noexcept; +}; + +static void test_traits() +{ + // just copy the static assertions from libstdc++'s test suite, call.cc, cons.cc, conv.cc + + // Check return types + BOOST_TEST_TRAIT_TRUE( ( is_same>> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_same>> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_same>> ) ); + + // With const qualifier + BOOST_TEST_TRAIT_FALSE( ( is_invocable< move_only_function const > ) ); + BOOST_TEST_TRAIT_FALSE( ( is_invocable< move_only_function const &> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function > ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function &> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function const > ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function const &> ) ); + + // With no ref-qualifier + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function > ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function &> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function > ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function &> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function const > ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function const &> ) ); + + // With & ref-qualifier + BOOST_TEST_TRAIT_FALSE( ( is_invocable< move_only_function > ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function &> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function > ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function &> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function const > ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function const &> ) ); + + // With && ref-qualifier + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function > ) ); + BOOST_TEST_TRAIT_FALSE( ( is_invocable< move_only_function &> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function > ) ); + BOOST_TEST_TRAIT_FALSE( ( is_invocable< move_only_function &> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_invocable< move_only_function const > ) ); + BOOST_TEST_TRAIT_FALSE( ( is_invocable< move_only_function const &> ) ); + + #if defined(__cpp_noexcept_function_type) + + // With noexcept-specifier + BOOST_TEST_TRAIT_FALSE( ( is_nothrow_invocable< move_only_function > ) ); + BOOST_TEST_TRAIT_FALSE( ( is_nothrow_invocable< move_only_function > ) ); + BOOST_TEST_TRAIT_TRUE( ( is_nothrow_invocable< move_only_function > ) ); + BOOST_TEST_TRAIT_TRUE( ( is_nothrow_invocable< move_only_function& > ) ); + + #endif + + BOOST_TEST_TRAIT_TRUE( ( std::is_nothrow_default_constructible> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_nothrow_constructible, std::nullptr_t> ) ); + BOOST_TEST_TRAIT_TRUE( ( std::is_nothrow_move_constructible> ) ); + BOOST_TEST_TRAIT_FALSE( ( std::is_copy_constructible> ) ); + + BOOST_TEST_TRAIT_TRUE( ( is_constructible, void()> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, void( & )()> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, void( * )()> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, int()> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, int( & )()> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, int( * )()> ) ); + BOOST_TEST_TRAIT_FALSE( ( is_constructible, void( int )> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, void( int )> ) ); + + BOOST_TEST_TRAIT_TRUE( ( is_constructible, in_place_type_t, void( int )> ) ); + + BOOST_TEST_TRAIT_TRUE( ( is_constructible, void() noexcept> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, void() noexcept> ) ); + +#if defined(__cpp_noexcept_function_type) + BOOST_TEST_TRAIT_FALSE( ( is_constructible, void()> ) ); +#endif + + BOOST_TEST_TRAIT_TRUE( ( is_constructible, Q> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, Q> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, Q> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, Q> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, Q> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, Q> ) ); + + BOOST_TEST_TRAIT_TRUE( ( is_constructible, R> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, R> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, R> ) ); + BOOST_TEST_TRAIT_FALSE( ( is_constructible, R> ) ); + BOOST_TEST_TRAIT_FALSE( ( is_constructible, R> ) ); + BOOST_TEST_TRAIT_FALSE( ( is_constructible, R> ) ); + + BOOST_TEST_TRAIT_TRUE( ( is_nothrow_constructible, F> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_nothrow_constructible, G> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_nothrow_constructible, G> ) ); + + BOOST_TEST_TRAIT_TRUE( ( is_nothrow_constructible, H> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_nothrow_constructible, H> ) ); + BOOST_TEST_TRAIT_FALSE( ( is_nothrow_constructible, in_place_type_t, int> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_nothrow_constructible, in_place_type_t, int, int> ) ); + + BOOST_TEST_TRAIT_TRUE( ( is_constructible, in_place_type_t, int, const char*> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_constructible, in_place_type_t, std::initializer_list> ) ); + + BOOST_TEST_TRAIT_FALSE( ( is_constructible, move_only_function> ) ); + BOOST_TEST_TRAIT_FALSE( ( is_constructible, move_only_function> ) ); + BOOST_TEST_TRAIT_FALSE( ( is_constructible, move_only_function> ) ); + BOOST_TEST_TRAIT_FALSE( ( is_constructible, move_only_function> ) ); + + using FuncType = int( int ); + + // Top level const qualifiers are ignored in function types, and decay + // is performed. + BOOST_TEST_TRAIT_TRUE( ( is_same, move_only_function> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_same, move_only_function> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_same, move_only_function> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_same, move_only_function> ) ); + BOOST_TEST_TRAIT_TRUE( ( is_same, move_only_function> ) ); +} + +static void test_conv() +{ + { + noex_callable nt; + + move_only_function f1( std::move( nt ) ); + move_only_function f2( std::move( f1 ) ); + BOOST_TEST_EQ( f2( 1234 ), 1235 ); + } + + { + auto l = []( noex_callable const& c ) noexcept { return *c.p_ + 1234; }; + + noex_callable c; + + move_only_function f1( l ); + BOOST_TEST_EQ( f1( c ), 1235 ); + + move_only_function f2( std::move( f1 ) ); + BOOST_TEST_EQ( f2( c ), 1235 ); + + move_only_function f3( std::move( f2 ) ); + BOOST_TEST_EQ( f3( c ), 1235 ); + + move_only_function f4( std::move( f3 ) ); + BOOST_TEST_EQ( f4( noex_callable{} ), 1235 ); + + move_only_function f5( std::move( f4 ) ); + BOOST_TEST_EQ( std::move( f5 )( noex_callable{} ), 1235 ); + + move_only_function f6( l ); + + move_only_function f7( std::move( f6 ) ); + BOOST_TEST_EQ( f7( noex_callable{} ), 1235 ); + + // TODO: libstdc++ includes this test case but it seems like pedantically calling a `long(*)(Arg)` should + // be UB in the general sense. We need to confirm one way or another if this is something we need to support. + // + // move_only_function f8( l ); + // move_only_function f9( std::move( f8 ) ); + // BOOST_TEST_EQ( f9( noex_callable{} ), 1235 ); + } + + { + move_only_function e; + BOOST_TEST( e == nullptr ); + + move_only_function e2( std::move( e ) ); + BOOST_TEST( e2 == nullptr ); + e2 = std::move( e ); + BOOST_TEST( e2 == nullptr ); + + move_only_function e3( std::move( e2 ) ); + BOOST_TEST( e3 == nullptr ); + } +} + +int main() +{ + test_call(); + test_traits(); + test_conv(); + + return boost::report_errors(); +}