diff --git a/CMakeLists.txt b/CMakeLists.txt index dbd9859..399d996 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,9 @@ endif() include( ${CMAKE_CURRENT_BINARY_DIR}/cmake/get_cpm.cmake ) -set( boost_ver boost-1.90.0 ) +# newest STABLE Boost release; deliberately not boost-1.92.0.beta1, and not +# master - a released tag is reproducible where a branch tip is not. +set( boost_ver boost-1.91.0 ) CPMAddPackage( "gh:boostorg/static_assert#${boost_ver}" ) CPMAddPackage( "gh:boostorg/throw_exception#${boost_ver}" ) CPMAddPackage( "gh:boostorg/config#${boost_ver}" ) diff --git a/include/psi/functionoid/detail/callable_base.hpp b/include/psi/functionoid/detail/callable_base.hpp index a84d748..48d62d4 100644 --- a/include/psi/functionoid/detail/callable_base.hpp +++ b/include/psi/functionoid/detail/callable_base.hpp @@ -101,24 +101,6 @@ struct function_obj_tag {}; struct member_ptr_tag {}; struct function_obj_ref_tag {}; -// When functions and function pointers are decorated with exception -// specifications MSVC mangles their type (almost) beyond recognition. -// Even MSVC supplied type traits is_pointer, is_member_pointer and -// is_function no longer recognize them. This tester is a workaround that -// seems to work well enough for now. -template -using is_msvc_exception_specified_function_pointer = std::integral_constant -< - bool, -#ifdef _MSC_VER - !std::is_class_v && - !std::is_fundamental_v && - ( sizeof( T ) == sizeof( void (*)() ) ) -#else - false -#endif ->; - template struct get_function_tag { @@ -746,20 +728,6 @@ vtable template using base_vtable = vtable, Traits>; -template -T get_default_value( std::false_type /*not a reference type*/ ) { return {}; } - -template <> -inline void get_default_value( std::false_type /*not a reference type*/ ) {} - -template -T get_default_value( std::true_type /*a reference type*/ ) -{ - using actual_type_t = std::remove_reference_t; - static T invalid_reference( *static_cast( 0 ) ); - return invalid_reference; -} - //////////////////////////////////////////////////////////////////////////// struct callable_tag {}; @@ -917,21 +885,26 @@ class callable_base : public callable_tag emptyHandler, empty_handler_vtable, empty_handler_vtable, - std::allocator(), - std::false_type() + std::allocator() ); } + // Whether the source is itself a callable is a property of its type, so the + // two implementations are constrained on it directly rather than selected + // by a tag the caller has to compute and pass. + template + static bool constexpr is_a_callable{ std::is_base_of_v>> }; + // Assignment from another functionoid. template + requires is_a_callable void assign ( FunctionObj && f, [[ maybe_unused ]] vtable const & functor_vtable, vtable const & empty_handler_vtable, - Allocator, - std::true_type // assignment of an instance of a callable (with possibly different traits) + Allocator ) { auto const same_traits{ std::is_convertible_v *, callable_base const *> }; @@ -962,23 +935,39 @@ class callable_base : public callable_tag // General actual assignment. template + requires ( !is_a_callable ) void assign ( FunctionObj && f, vtable const & functor_vtable, vtable const & empty_handler_vtable, - Allocator, - std::false_type /*generic assign*/ + Allocator ); + // Whether the target can be assigned in place, without a fallible + // intermediate, depends only on F and the buffer - so name the condition + // once and constrain the two implementations on it, instead of recomputing + // it at the call site and threading it in as an integral_constant tag. + /// \todo This can/should be rewritten because the + /// small-object-optimization condition is too strict, even heap + /// allocated targets can be assigned directly because they have a + /// nothrow swap operation. + /// (28.10.2010.) (Domagoj Saric) + template + static bool constexpr has_no_fail_assignment + { + functor_traits::allowsSmallObjectOptimization && + std::is_nothrow_assignable_v, F> + }; + template + requires has_no_fail_assignment void actual_assign ( F && f, vtable const & functor_vtable, vtable const & /*empty_handler_vtable*/, - Allocator const a, - std::true_type /*can use direct assign*/ + Allocator const a ) noexcept { using functor_manager = functor_manager; @@ -988,13 +977,13 @@ class callable_base : public callable_tag } template + requires ( !has_no_fail_assignment ) void actual_assign ( F && f, vtable const & functor_vtable, vtable const & empty_handler_vtable, - Allocator const a, - std::false_type /*must use safe assignment*/ + Allocator const a ) { // This most generic case needs to be reworked [currently does redundant @@ -1017,7 +1006,7 @@ class callable_base : public callable_tag void assign_functionoid_direct( callable_base && source, vtable const & empty_handler_vtable ) noexcept( ( Traits::moveable >= support_level::nofail ) || ( Traits::moveable == support_level::na && Traits::copyable >= support_level::nofail ) ) { - source.move_to( *this, std::integral_constant{} ); + source.move_to( *this ); this ->p_vtable_ = &source.get_vtable(); source.p_vtable_ = &empty_handler_vtable; } @@ -1106,11 +1095,15 @@ class callable_base : public callable_tag // empty handler's vtable will correctly handle it. void destroy() noexcept { get_vtable().destroy( this->functor_ ); } - void move_to( callable_base & destination, std::true_type /* has move*/ ) const noexcept( Traits::moveable >= support_level::nofail ) + // Whether a move exists is a property of Traits alone, so constrain on it + // directly instead of threading an integral_constant tag through the call. + void move_to( callable_base & destination ) const noexcept( Traits::moveable >= support_level::nofail ) + requires ( Traits::moveable != support_level::na ) { get_vtable().move ( std::move( this->functor_ ), destination.functor_ ); } - void move_to( callable_base & destination, std::false_type /*not has move*/ ) const noexcept( Traits::copyable >= support_level::nofail ) + void move_to( callable_base & destination ) const noexcept( Traits::copyable >= support_level::nofail ) + requires ( Traits::moveable == support_level::na ) { get_vtable().clone( std::move( this->functor_ ), destination.functor_ ); } @@ -1191,7 +1184,7 @@ class callable_base::safe_mover_base static void move( callable_base & source, callable_base & destination, vtable const & empty_handler_vtable ) noexcept { - source.move_to( destination, std::integral_constant{} ); + source.move_to( destination ); destination.p_vtable_ = source.p_vtable_; source .p_vtable_ = &empty_handler_vtable; } @@ -1241,13 +1234,13 @@ void callable_base::swap( callable_base & other, vtable const & empty_ha template template + requires ( !callable_base::template is_a_callable ) void callable_base::assign ( F && f, vtable const & functor_vtable, vtable const & empty_handler_vtable, - Allocator const a, - std::false_type /*generic assign*/ + Allocator const a ) { using namespace detail; @@ -1270,25 +1263,12 @@ void callable_base::assign } else { - /// \todo This can/should be rewritten because the - /// small-object-optimization condition is too strict, even heap - /// allocated targets can be assigned directly because they have a - /// nothrow swap operation. - /// (28.10.2010.) (Domagoj Saric) - using has_no_fail_assignement_t = std::integral_constant - < - bool, - functor_traits::allowsSmallObjectOptimization && - std::is_nothrow_assignable_v, F> - >; - actual_assign ( std::forward( f ), functor_vtable, empty_handler_vtable, - a, - has_no_fail_assignement_t{} + a ); } } // void callable_base::assign() diff --git a/include/psi/functionoid/functionoid.hpp b/include/psi/functionoid/functionoid.hpp index 4f796d3..eae79e2 100644 --- a/include/psi/functionoid/functionoid.hpp +++ b/include/psi/functionoid/functionoid.hpp @@ -102,7 +102,7 @@ class callable struct no_eh_state_constructor { template - base_vtable const & operator()( function_base & base, F && f, Allocator const a ) const noexcept( std::is_nothrow_constructible, F>::value ) + base_vtable const & operator()( function_base & base, F && f, Allocator const a ) const noexcept( std::is_nothrow_constructible_v, F> ) { detail::debug_clear( base ); static_cast( base ).do_assign( std::forward( f ), a ); @@ -110,7 +110,7 @@ class callable } template - base_vtable const & operator()( function_base & base, F && f ) const noexcept( std::is_nothrow_constructible, F>::value ) + base_vtable const & operator()( function_base & base, F && f ) const noexcept( std::is_nothrow_constructible_v, F> ) { using NakedFunctionObj = std::remove_const_t>; return (*this)( base, std::forward( f ), typename Traits:: template allocator() ); @@ -122,8 +122,12 @@ class callable public: // Public function interface. callable() noexcept : function_base( empty_handler_vtable(), empty_handler{} ) {} - template // SFINAE/enable if required by MSVC 16 for construction from a callable & (mutable reference) - callable( Functor && f, std::enable_if_t< !std::is_same_v< std::decay_t, callable > > * = nullptr ) noexcept( std::is_nothrow_constructible_v, Functor> /*...mrmlj...&& !is_heap_allocated*/ ) + // Constrained (rather than SFINAE'd through a defaulted dummy parameter) + // so it does not hide the copy/move constructors for a `callable &` - the + // original reason this needed excluding at all. + template + requires ( !std::is_same_v, callable> ) + callable( Functor && f ) noexcept( std::is_nothrow_constructible_v, Functor> /*...mrmlj...&& !is_heap_allocated*/ ) : function_base( no_eh_state_construction_trick_tag{}, no_eh_state_constructor{}, std::forward( f ) ) {} template @@ -182,8 +186,9 @@ class callable // for the signature template parameter to be the same (and therefor the vtable is the same, with // a possible exception being the case of an empty source as empty handler vtables depend on the // policy as well as the signature). - template - static vtable_type const & vtable_for_functor_aux( std::true_type /*is a callable*/, callable const & functor ) + template + requires std::is_base_of_v + static vtable_type const & vtable_for_functor( StoredFunctor const & functor ) { static_assert( std::is_base_of_v> ); return functor.vtable(); @@ -197,10 +202,11 @@ class callable template requires ( + !std::is_base_of_v && ( std::is_copy_constructible_v || Traits::copyable == support_level::na ) && ( std::is_nothrow_copy_constructible_v || Traits::copyable == support_level::na || Traits::copyable == support_level::supported ) ) - static vtable_type const & vtable_for_functor_aux( std::false_type /*is not a callable*/, StoredFunctor const & /*functor*/ ) + static vtable_type const & vtable_for_functor( StoredFunctor const & /*functor*/ ) { using namespace detail; @@ -208,12 +214,12 @@ class callable // my_empty_handler (anti-code-bloat) because they only differ in the // operator() member function which is irrelevant for/not used by the // manager. - using is_empty_handler = std::is_same; + static bool constexpr is_empty_handler{ std::is_same_v }; using manager_type = functor_manager < std::conditional_t < - is_empty_handler::value, + is_empty_handler, ActualFunctor, StoredFunctor >, @@ -223,9 +229,9 @@ class callable static_assert ( - std::is_same::value + std::is_same_v == - std::is_same::value + std::is_same_v ); using invoker_type = invoker; @@ -241,20 +247,10 @@ class callable static_cast( nullptr ), static_cast( nullptr ), static_cast( nullptr ), - is_empty_handler::value + is_empty_handler }; return the_vtable; - } // vtable_for_functor_aux() - - template - static vtable_type const & vtable_for_functor( StoredFunctor const & functor ) - { - return vtable_for_functor_aux - ( - std::is_base_of(), - functor - ); - } + } // vtable_for_functor() // ...direct actually means whether to skip pre-destruction (when not // assigning but constructing) so it should probably be renamed to @@ -304,8 +300,7 @@ class callable std::forward( stored_functor ), vtable_for_functor( stored_functor ), empty_handler_vtable(), - StoredFunctorAllocator( a ), - std::is_base_of{} /*are we assigning another callable?*/ + StoredFunctorAllocator( a ) ); } }; // class callable diff --git a/include/psi/functionoid/rtti.hpp b/include/psi/functionoid/rtti.hpp index 23de875..594d7f6 100644 --- a/include/psi/functionoid/rtti.hpp +++ b/include/psi/functionoid/rtti.hpp @@ -55,8 +55,8 @@ class typed_functor : p_functor_ ( std::addressof ( functor ) ), type_id_ ( BOOST_CORE_TYPEID( Functor ) ), - const_qualified_ ( std::is_const ::value ), - volatile_qualified_( std::is_volatile ::value ) + const_qualified_ ( std::is_const_v ), + volatile_qualified_( std::is_volatile_v ) {} boost::core::typeinfo const & functor_type_info() const noexcept { return type_id_; } @@ -69,8 +69,8 @@ class typed_functor get_functor_if_types_match ( BOOST_CORE_TYPEID( Functor ), - std::is_const ::value, - std::is_volatile ::value + std::is_const_v , + std::is_volatile_v ) ); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 75e9375..acc0553 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,8 @@ -CPMAddPackage( "gh:google/googletest@1.15.2" ) +# master, not the 1.17.0 release: releases up to and including it print a +# char8_t by ImplicitCast_ to char32_t, which recent clang rejects under +# -Wcharacter-conversion (googletest#4762 - fixed on master by switching to +# static_cast). +CPMAddPackage( "gh:google/googletest#main" ) include( GoogleTest ) add_executable( functionoid_smoke