From 1002fe77bd8cce72ad03d75e50894c03ffab9ede Mon Sep 17 00:00:00 2001 From: Kefu Chai Date: Wed, 16 Jul 2025 13:59:01 +0800 Subject: [PATCH] include/function2.hpp: avoid using std::aligned_storage_t std::aligned_storage_t was deprecated in C++23, now that we've switched to C++23. let's address the warning like: ``` In file included from /mnt/igor/github/salieri11/ceph/src/osdc/Objecter.cc:15: In file included from /mnt/igor/github/salieri11/ceph/src/osdc/Objecter.h:44: /mnt/igor/github/salieri11/ceph/src/include/function2.hpp:962:10: error: 'aligned_storage_t' is deprecated [-Werror,-Wdeprecated-declarations] 962 | std::aligned_storage_t capacity_; ``` in this change, we - update function2.hpp with upstream - apply the fix to trade std::aligned_storage_t with an alignas-based equivalent implementation Signed-off-by: Kefu Chai --- src/include/function2.hpp | 600 +++++++++++++++++++++++++++----------- 1 file changed, 434 insertions(+), 166 deletions(-) diff --git a/src/include/function2.hpp b/src/include/function2.hpp index 5419e143496db..6453f706d2a6d 100644 --- a/src/include/function2.hpp +++ b/src/include/function2.hpp @@ -1,5 +1,5 @@ -// Copyright 2015-2018 Denis Blank +// Copyright 2015-2020 Denis Blank // Distributed under the Boost Software License, Version 1.0 // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -8,6 +8,7 @@ #define FU2_INCLUDED_FUNCTION2_HPP_ #include +#include #include #include #include @@ -34,12 +35,18 @@ #endif #endif #endif // FU2_WITH_DISABLED_EXCEPTIONS +// - FU2_HAS_LIMITED_EMPTY_PROPAGATION +#if defined(FU2_WITH_LIMITED_EMPTY_PROPAGATION) +#define FU2_HAS_LIMITED_EMPTY_PROPAGATION +#endif // FU2_WITH_NO_EMPTY_PROPAGATION // - FU2_HAS_NO_FUNCTIONAL_HEADER -#if !defined(FU2_WITH_NO_FUNCTIONAL_HEADER) || \ - !defined(FU2_NO_FUNCTIONAL_HEADER) || \ - !defined(FU2_HAS_DISABLED_EXCEPTIONS) -#define FU2_HAS_NO_FUNCTIONAL_HEADER +#if !defined(FU2_WITH_NO_FUNCTIONAL_HEADER) && \ + !defined(FU2_NO_FUNCTIONAL_HEADER) && \ + (!defined(FU2_HAS_DISABLED_EXCEPTIONS) || \ + defined(FU2_HAS_LIMITED_EMPTY_PROPAGATION)) #include +#else +#define FU2_HAS_NO_FUNCTIONAL_HEADER #endif // - FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE #if defined(FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE) @@ -56,12 +63,75 @@ #endif #endif // FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE +// - FU2_HAS_NO_EMPTY_PROPAGATION +#if defined(FU2_WITH_NO_EMPTY_PROPAGATION) +#define FU2_HAS_NO_EMPTY_PROPAGATION +#endif // FU2_WITH_NO_EMPTY_PROPAGATION + #if !defined(FU2_HAS_DISABLED_EXCEPTIONS) #include #endif +#if defined(__cpp_constexpr) && (__cpp_constexpr >= 201304) +#define FU2_DETAIL_CXX14_CONSTEXPR constexpr +#elif defined(__clang__) && defined(__has_feature) +#if __has_feature(__cxx_generic_lambdas__) && \ + __has_feature(__cxx_relaxed_constexpr__) +#define FU2_DETAIL_CXX14_CONSTEXPR constexpr +#endif +#elif defined(_MSC_VER) && (_MSC_VER >= 1915) && (_MSVC_LANG >= 201402) +#define FU2_DETAIL_CXX14_CONSTEXPR constexpr +#endif +#ifndef FU2_DETAIL_CXX14_CONSTEXPR +#define FU2_DETAIL_CXX14_CONSTEXPR +#endif + +/// Hint for the compiler that this point should be unreachable +#if defined(_MSC_VER) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __assume(false) +#elif defined(__GNUC__) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable() +#elif defined(__has_builtin) +#if __has_builtin(__builtin_unreachable) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable() +#endif +#endif +#ifndef FU2_DETAIL_UNREACHABLE_INTRINSIC +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE_INTRINSIC() abort() +#endif + +/// Causes the application to exit abnormally +#if defined(_MSC_VER) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_TRAP() __debugbreak() +#elif defined(__GNUC__) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_TRAP() __builtin_trap() +#elif defined(__has_builtin) +#if __has_builtin(__builtin_trap) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_TRAP() __builtin_trap() +#endif +#endif +#ifndef FU2_DETAIL_TRAP +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_TRAP() *(volatile int*)0x11 = 0 +#endif + +#ifndef NDEBUG +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE() ::fu2::detail::unreachable_debug() +#else +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE() FU2_DETAIL_UNREACHABLE_INTRINSIC() +#endif + namespace fu2 { -inline namespace abi_310 { +inline namespace abi_400 { namespace detail { template class function; @@ -69,7 +139,7 @@ class function; template struct identity {}; -// Equivalent to C++17's std::void_t which is targets a bug in GCC, +// Equivalent to C++17's std::void_t which targets a bug in GCC, // that prevents correct SFINAE behavior. // See http://stackoverflow.com/questions/35753920 for details. template @@ -78,6 +148,22 @@ struct deduce_to_void : std::common_type {}; template using void_t = typename deduce_to_void::type; +template +using unrefcv_t = std::remove_cv_t>; + +template +struct lazy_and; + +template +struct lazy_and : B1 {}; + +template +struct lazy_and : std::conditional::type {}; + +// template +// struct lazy_and +// : std::conditional, B1>::type {}; + // Copy enabler helper class template struct copyable {}; @@ -92,9 +178,9 @@ struct copyable { }; /// Configuration trait to configure the function_base class. -template +template struct config { - // Is true if the function is copyable. + // Is true if the function is owning. static constexpr auto const is_owning = Owning; // Is true if the function is copyable. @@ -102,7 +188,9 @@ struct config { // The internal capacity of the function // used in small functor optimization. - static constexpr auto const capacity = Capacity; + // The object shall expose the real capacity through Capacity::capacity + // and the intended alignment through Capacity::alignment. + using capacity = Capacity; }; /// A config which isn't compatible to other configs @@ -112,9 +200,17 @@ struct property { static constexpr auto const is_throwing = Throws; // Is true when the function throws an exception on empty invocation. - static constexpr auto const is_strong_exception_guaranteed = Throws; + static constexpr auto const is_strong_exception_guaranteed = + HasStrongExceptGuarantee; }; +#ifndef NDEBUG +[[noreturn]] inline void unreachable_debug() { + FU2_DETAIL_TRAP(); + std::abort(); +} +#endif + /// Provides utilities for invocing callable objects namespace invocation { /// Invokes the given callable object with the given arguments @@ -137,8 +233,8 @@ constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept( template constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept( noexcept((std::forward(self)->*member)(std::forward(args)...))) - -> decltype( - (std::forward(self)->*member)(std::forward(args)...)) { + -> decltype((std::forward(self)->*member)( + std::forward(args)...)) { return (std::forward(self)->*member)(std::forward(args)...); } /// Invokes the given pointer to a scalar member by reference @@ -174,14 +270,14 @@ struct can_invoke, std::declval()...)))> : std::true_type {}; template struct can_invoke, - decltype( - (void)((std::declval().*std::declval())( - std::declval()...)))> : std::true_type {}; + decltype(( + void)((std::declval().*std::declval())( + std::declval()...)))> : std::true_type {}; template struct can_invoke, - decltype( - (void)((std::declval()->*std::declval())( - std::declval()...)))> : std::true_type {}; + decltype(( + void)((std::declval()->*std::declval())( + std::declval()...)))> : std::true_type {}; template struct can_invoke, decltype((void)(std::declval().*std::declval()))> @@ -193,16 +289,17 @@ struct can_invoke, }; template struct can_invoke, - decltype( - (void)(std::declval()->*std::declval()))> + decltype(( + void)(std::declval()->*std::declval()))> : std::true_type {}; template struct is_noexcept_correct : std::true_type {}; template struct is_noexcept_correct> - : std::integral_constant(), - std::declval()...))> { + : std::integral_constant(), std::declval()...))> { }; } // end namespace invocation @@ -213,8 +310,8 @@ template struct overload_impl : Current, overload_impl { explicit overload_impl(Current current, Next next, Rest... rest) - : Current(std::move(current)), overload_impl( - std::move(next), std::move(rest)...) { + : Current(std::move(current)), + overload_impl(std::move(next), std::move(rest)...) { } using Current::operator(); @@ -241,7 +338,7 @@ namespace type_erasure { template struct address_taker { template - static void* take(O&& obj) { + static auto take(O&& obj) { return std::addressof(obj); } static T& restore(void* ptr) { @@ -279,8 +376,8 @@ struct box : private Allocator { T value_; - explicit box(T value, Allocator allocator) - : Allocator(std::move(allocator)), value_(std::move(value)) { + explicit box(T value, Allocator allocator_) + : Allocator(std::move(allocator_)), value_(std::move(value)) { } box(box&&) = default; @@ -295,8 +392,8 @@ struct box : private Allocator { T value_; - explicit box(T value, Allocator allocator) - : Allocator(std::move(allocator)), value_(std::move(value)) { + explicit box(T value, Allocator allocator_) + : Allocator(std::move(allocator_)), value_(std::move(value)) { } box(box&&) = default; @@ -315,28 +412,27 @@ struct box_factory> { /// Allocates space through the boxed allocator static box* box_allocate(box const* me) { - real_allocator allocator(*static_cast(me)); + real_allocator allocator_(*static_cast(me)); return static_cast*>( - std::allocator_traits::allocate(allocator, 1U)); + std::allocator_traits::allocate(allocator_, 1U)); } /// Destroys the box through the given allocator static void box_deallocate(box* me) { - real_allocator allocator(*static_cast(me)); + real_allocator allocator_(*static_cast(me)); me->~box(); - std::allocator_traits::deallocate(allocator, me, 1U); + std::allocator_traits::deallocate(allocator_, me, 1U); } }; /// Creates a box containing the given value and allocator -template >> +template auto make_box(std::integral_constant, T&& value, - Allocator&& allocator = Allocator{}) { - return box, std::decay_t>{ - std::forward(value), std::forward(allocator)}; + Allocator&& allocator_) { + return box, std::decay_t>( + std::forward(value), std::forward(allocator_)); } template @@ -352,6 +448,16 @@ union data_accessor { } explicit constexpr data_accessor(void* ptr) noexcept : ptr_(ptr) { } + explicit constexpr data_accessor(void const* ptr) noexcept + : data_accessor(const_cast(ptr)) { + } + + constexpr void assign_ptr(void* ptr) noexcept { + ptr_ = ptr; + } + constexpr void assign_ptr(void const* ptr) noexcept { + ptr_ = const_cast(ptr); + } /// The pointer we use if the object is on the heap void* ptr_; @@ -360,7 +466,8 @@ union data_accessor { }; /// See opcode::op_fetch_empty -constexpr void write_empty(data_accessor* accessor, bool empty) noexcept { +static FU2_DETAIL_CXX14_CONSTEXPR void write_empty(data_accessor* accessor, + bool empty) noexcept { accessor->inplace_storage_ = std::size_t(empty); } @@ -375,8 +482,9 @@ using transfer_volatile_t = /// The retriever when the object is allocated inplace template -constexpr auto retrieve(std::true_type /*is_inplace*/, Accessor from, - std::size_t from_capacity) { +FU2_DETAIL_CXX14_CONSTEXPR auto retrieve(std::true_type /*is_inplace*/, + Accessor from, + std::size_t from_capacity) { using type = transfer_const_t>*; /// Process the command by using the data inside the internal capacity @@ -405,13 +513,13 @@ struct bad_function_call : std::exception { return "bad function call"; } }; -#elif +#else using std::bad_function_call; #endif #endif #ifdef FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE -#define FU2_EXPAND_QUALIFIERS_NOEXCEPT(F) \ +#define FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F) \ F(, , noexcept, , &) \ F(const, , noexcept, , &) \ F(, volatile, noexcept, , &) \ @@ -424,11 +532,17 @@ using std::bad_function_call; F(const, , noexcept, &&, &&) \ F(, volatile, noexcept, &&, &&) \ F(const, volatile, noexcept, &&, &&) +#define FU2_DETAIL_EXPAND_CV_NOEXCEPT(F) \ + F(, , noexcept) \ + F(const, , noexcept) \ + F(, volatile, noexcept) \ + F(const, volatile, noexcept) #else // FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE -#define FU2_EXPAND_QUALIFIERS_NOEXCEPT(F) +#define FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F) +#define FU2_DETAIL_EXPAND_CV_NOEXCEPT(F) #endif // FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE -#define FU2_EXPAND_QUALIFIERS(F) \ +#define FU2_DETAIL_EXPAND_QUALIFIERS(F) \ F(, , , , &) \ F(const, , , , &) \ F(, volatile, , , &) \ @@ -441,7 +555,13 @@ using std::bad_function_call; F(const, , , &&, &&) \ F(, volatile, , &&, &&) \ F(const, volatile, , &&, &&) \ - FU2_EXPAND_QUALIFIERS_NOEXCEPT(F) + FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F) +#define FU2_DETAIL_EXPAND_CV(F) \ + F(, , ) \ + F(const, , ) \ + F(, volatile, ) \ + F(const, volatile, ) \ + FU2_DETAIL_EXPAND_CV_NOEXCEPT(F) /// If the function is qualified as noexcept, the call will never throw template @@ -515,7 +635,7 @@ using is_noexcept_noexcept = std::true_type; }; \ }; -FU2_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT) +FU2_DETAIL_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT) #undef FU2_DEFINE_FUNCTION_TRAIT /// Deduces to the function pointer to the given signature @@ -666,7 +786,9 @@ class operator_impl; auto parent = static_cast(this); \ using erasure_t = std::decay_terasure_)>; \ \ - return erasure_t::template invoke( \ + /* `std::decay_terasure_)>` is a workaround for a */ \ + /* compiler regression of MSVC 16.3.1, see #29 for details. */ \ + return std::decay_terasure_)>::template invoke( \ static_cast(parent->erasure_), \ std::forward(args)...); \ } \ @@ -675,7 +797,7 @@ class operator_impl; typename Ret, typename... Args> \ class operator_impl, \ Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT> \ - : copyable { \ + : copyable { \ \ template \ friend class operator_impl; \ @@ -693,25 +815,27 @@ class operator_impl; static_cast CONST VOLATILE*>(this); \ using erasure_t = std::decay_terasure_)>; \ \ - return erasure_t::template invoke( \ + /* `std::decay_terasure_)>` is a workaround for a */ \ + /* compiler regression of MSVC 16.3.1, see #29 for details. */ \ + return std::decay_terasure_)>::template invoke( \ static_cast(parent->erasure_), \ std::forward(args)...); \ } \ }; -FU2_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT) +FU2_DETAIL_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT) #undef FU2_DEFINE_FUNCTION_TRAIT } // namespace invocation_table namespace tables { /// Identifies the action which is dispatched on the erased object enum class opcode { - op_move, //< Move the object and set the vtable - op_copy, //< Copy the object and set the vtable - op_destroy, //< Destroy the object and reset the vtable - op_weak_destroy, //< Destroy the object without resetting the vtable - op_fetch_empty, //< Stores true or false into the to storage - //< to indicate emptiness + op_move, ///< Move the object and set the vtable + op_copy, ///< Copy the object and set the vtable + op_destroy, ///< Destroy the object and reset the vtable + op_weak_destroy, ///< Destroy the object without resetting the vtable + op_fetch_empty, ///< Stores true or false into the to storage + ///< to indicate emptiness }; /// Abstraction for a vtable together with a command table @@ -811,9 +935,7 @@ class vtable> { } } - // TODO Use an unreachable intrinsic - assert(false && "Unreachable!"); - std::exit(-1); + FU2_DETAIL_UNREACHABLE(); } template @@ -862,6 +984,9 @@ class vtable> { write_empty(to, true); break; } + default: { + FU2_DETAIL_UNREACHABLE(); + } } } @@ -916,13 +1041,13 @@ public: /// Invoke the function at the given index template - constexpr auto invoke(Args&&... args) const { + constexpr decltype(auto) invoke(Args&&... args) const { auto thunk = invoke_table_t::template fetch(vtable_); return thunk(std::forward(args)...); } /// Invoke the function at the given index template - constexpr auto invoke(Args&&... args) const volatile { + constexpr decltype(auto) invoke(Args&&... args) const volatile { auto thunk = invoke_table_t::template fetch(vtable_); return thunk(std::forward(args)...); } @@ -952,26 +1077,28 @@ public: /// same space with the internal capacity. /// The storage type is distinguished by multiple versions of the /// control and vtable. -template +template struct internal_capacity { /// We extend the union through a technique similar to the tail object hack typedef union { /// Tag to access the structure in a type-safe way data_accessor accessor_; /// The internal capacity we use to allocate in-place - std::aligned_storage_t capacity_; + struct alignas(Capacity::alignment) { + unsigned char data[Capacity::capacity]; + } capacity_; } type; }; -template -struct internal_capacity> { +template +struct internal_capacity< + Capacity, std::enable_if_t<(Capacity::capacity < sizeof(void*))>> { typedef struct { /// Tag to access the structure in a type-safe way data_accessor accessor_; } type; }; -template +template class internal_capacity_holder { // Tag to access the structure in a type-safe way typename internal_capacity::type storage_; @@ -979,13 +1106,14 @@ class internal_capacity_holder { public: constexpr internal_capacity_holder() = default; - constexpr data_accessor* opaque_ptr() noexcept { + FU2_DETAIL_CXX14_CONSTEXPR data_accessor* opaque_ptr() noexcept { return &storage_.accessor_; } constexpr data_accessor const* opaque_ptr() const noexcept { return &storage_.accessor_; } - constexpr data_accessor volatile* opaque_ptr() volatile noexcept { + FU2_DETAIL_CXX14_CONSTEXPR data_accessor volatile* + opaque_ptr() volatile noexcept { return &storage_.accessor_; } constexpr data_accessor const volatile* opaque_ptr() const volatile noexcept { @@ -999,7 +1127,7 @@ public: /// An owning erasure template -class erasure : internal_capacity_holder { +class erasure : internal_capacity_holder { template friend class erasure; template @@ -1012,56 +1140,74 @@ class erasure : internal_capacity_holder { public: /// Returns the capacity of this erasure static constexpr std::size_t capacity() noexcept { - return internal_capacity_holder::capacity(); + return internal_capacity_holder::capacity(); } - constexpr erasure() noexcept { + FU2_DETAIL_CXX14_CONSTEXPR erasure() noexcept { vtable_.set_empty(); } - constexpr erasure(std::nullptr_t) noexcept { + FU2_DETAIL_CXX14_CONSTEXPR erasure(std::nullptr_t) noexcept { vtable_.set_empty(); } - constexpr erasure(erasure&& right) noexcept( - Property::is_strong_exception_guaranteed) { + FU2_DETAIL_CXX14_CONSTEXPR + erasure(erasure&& right) noexcept(Property::is_strong_exception_guaranteed) { right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(), this->opaque_ptr(), capacity()); } - constexpr erasure(erasure const& right) { + FU2_DETAIL_CXX14_CONSTEXPR erasure(erasure const& right) { right.vtable_.copy(vtable_, right.opaque_ptr(), right.capacity(), this->opaque_ptr(), capacity()); } template - constexpr erasure(erasure right) noexcept( + FU2_DETAIL_CXX14_CONSTEXPR + erasure(erasure right) noexcept( Property::is_strong_exception_guaranteed) { right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(), this->opaque_ptr(), capacity()); } template >> - constexpr erasure(T&& callable, Allocator&& allocator = Allocator{}) { + FU2_DETAIL_CXX14_CONSTEXPR erasure(std::false_type /*use_bool_op*/, + T&& callable, + Allocator&& allocator_ = Allocator{}) { vtable_t::init(vtable_, type_erasure::make_box( std::integral_constant{}, std::forward(callable), - std::forward(allocator)), + std::forward(allocator_)), this->opaque_ptr(), capacity()); } + template >> + FU2_DETAIL_CXX14_CONSTEXPR erasure(std::true_type /*use_bool_op*/, + T&& callable, + Allocator&& allocator_ = Allocator{}) { + if (!!callable) { + vtable_t::init(vtable_, + type_erasure::make_box( + std::integral_constant{}, + std::forward(callable), + std::forward(allocator_)), + this->opaque_ptr(), capacity()); + } else { + vtable_.set_empty(); + } + } ~erasure() { vtable_.weak_destroy(this->opaque_ptr(), capacity()); } - constexpr erasure& + FU2_DETAIL_CXX14_CONSTEXPR erasure& operator=(std::nullptr_t) noexcept(Property::is_strong_exception_guaranteed) { vtable_.destroy(this->opaque_ptr(), capacity()); return *this; } - constexpr erasure& operator=(erasure&& right) noexcept( + FU2_DETAIL_CXX14_CONSTEXPR erasure& operator=(erasure&& right) noexcept( Property::is_strong_exception_guaranteed) { vtable_.weak_destroy(this->opaque_ptr(), capacity()); right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(), @@ -1069,7 +1215,7 @@ public: return *this; } - constexpr erasure& operator=(erasure const& right) { + FU2_DETAIL_CXX14_CONSTEXPR erasure& operator=(erasure const& right) { vtable_.weak_destroy(this->opaque_ptr(), capacity()); right.vtable_.copy(vtable_, right.opaque_ptr(), right.capacity(), this->opaque_ptr(), capacity()); @@ -1077,7 +1223,7 @@ public: } template - constexpr erasure& + FU2_DETAIL_CXX14_CONSTEXPR erasure& operator=(erasure right) noexcept( Property::is_strong_exception_guaranteed) { vtable_.weak_destroy(this->opaque_ptr(), capacity()); @@ -1086,26 +1232,27 @@ public: return *this; } - template - constexpr erasure& operator=(T&& callable) { + template >> + void assign(std::false_type /*use_bool_op*/, T&& callable, + Allocator&& allocator_ = {}) { vtable_.weak_destroy(this->opaque_ptr(), capacity()); vtable_t::init(vtable_, type_erasure::make_box( std::integral_constant{}, - std::forward(callable)), + std::forward(callable), + std::forward(allocator_)), this->opaque_ptr(), capacity()); - return *this; } - template - void assign(T&& callable, Allocator&& allocator) { - vtable_.weak_destroy(this->opaque_ptr(), capacity()); - vtable_t::init(vtable_, - type_erasure::make_box( - std::integral_constant{}, - std::forward(callable), - std::forward(allocator)), - this->opaque_ptr(), capacity()); + template >> + void assign(std::true_type /*use_bool_op*/, T&& callable, + Allocator&& allocator_ = {}) { + if (!!callable) { + assign(std::false_type{}, std::forward(callable), + std::forward(allocator_)); + } else { + operator=(nullptr); + } } /// Returns true when the erasure doesn't hold any erased object @@ -1118,7 +1265,7 @@ public: /// We define this out of class to be able to forward the qualified /// erasure correctly. template - static constexpr auto invoke(Erasure&& erasure, Args&&... args) { + static constexpr decltype(auto) invoke(Erasure&& erasure, Args&&... args) { auto const capacity = erasure.capacity(); return erasure.vtable_.template invoke( std::forward(erasure).opaque_ptr(), capacity, @@ -1174,11 +1321,16 @@ public: template // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) - constexpr erasure(T&& object) + constexpr erasure(std::false_type /*use_bool_op*/, T&& object) : invoke_table_(invoke_table_t::template get_invocation_view_table_of< std::decay_t>()), view_(address_taker>::take(std::forward(object))) { } + template + // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) + FU2_DETAIL_CXX14_CONSTEXPR erasure(std::true_type use_bool_op, T&& object) { + this->assign(use_bool_op, std::forward(object)); + } ~erasure() = default; @@ -1208,11 +1360,19 @@ public: } template - constexpr erasure& operator=(T&& object) { + constexpr void assign(std::false_type /*use_bool_op*/, T&& callable) { invoke_table_ = invoke_table_t::template get_invocation_view_table_of< std::decay_t>(); - view_.ptr_ = address_taker>::take(std::forward(object)); - return *this; + view_.assign_ptr( + address_taker>::take(std::forward(callable))); + } + template + constexpr void assign(std::true_type /*use_bool_op*/, T&& callable) { + if (!!callable) { + assign(std::false_type{}, std::forward(callable)); + } else { + operator=(nullptr); + } } /// Returns true when the erasure doesn't hold any erased object @@ -1221,7 +1381,7 @@ public: } template - static constexpr auto invoke(Erasure&& erasure, T&&... args) { + static constexpr decltype(auto) invoke(Erasure&& erasure, T&&... args) { auto thunk = invoke_table_t::template fetch(erasure.invoke_table_); return thunk(&(erasure.view_), 0UL, std::forward(args)...); } @@ -1234,13 +1394,12 @@ template > struct accepts_one - : std::integral_constant< - bool, invocation::can_invoke, - typename Trait::arguments>::value && - invocation::is_noexcept_correct< - Trait::is_noexcept::value, - typename Trait::template callable, - typename Trait::arguments>::value> {}; + : detail::lazy_and< // both are std::integral_constant + invocation::can_invoke, + typename Trait::arguments>, + invocation::is_noexcept_correct, + typename Trait::arguments>> {}; /// Deduces to a true_type if the type T provides all signatures template @@ -1251,9 +1410,68 @@ struct accepts_all< void_t::value>...>> : std::true_type {}; +#if defined(FU2_HAS_NO_EMPTY_PROPAGATION) +template +struct use_bool_op : std::false_type {}; +#elif defined(FU2_HAS_LIMITED_EMPTY_PROPAGATION) +/// Implementation for use_bool_op based on the behaviour of std::function, +/// propagating empty state for pointers, `std::function` and +/// `fu2::detail::function` types only. +template +struct use_bool_op : std::false_type {}; + +#if !defined(FU2_HAS_NO_FUNCTIONAL_HEADER) +template +struct use_bool_op> : std::true_type {}; +#endif + +template +struct use_bool_op> : std::true_type {}; + +template +struct use_bool_op : std::true_type {}; + +template +struct use_bool_op : std::true_type {}; +#else +template +struct has_bool_op : std::false_type {}; +template +struct has_bool_op()))>> + : std::true_type { +#ifndef NDEBUG + static_assert(!std::is_pointer::value, + "Missing deduction for function pointer!"); +#endif +}; + +/// Deduces to a true_type if the type T is implementing operator bool() +/// or if the type is convertible to bool directly, this also implements an +/// optimizations for function references `void(&)()` which are can never +/// be null and for such a conversion to bool would never return false. +template +struct use_bool_op : has_bool_op {}; + +#define FU2_DEFINE_USE_OP_TRAIT(CONST, VOLATILE, NOEXCEPT) \ + template \ + struct use_bool_op \ + : std::true_type {}; + +FU2_DETAIL_EXPAND_CV(FU2_DEFINE_USE_OP_TRAIT) +#undef FU2_DEFINE_USE_OP_TRAIT + +template +struct use_bool_op : std::false_type {}; + +#if defined(FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE) +template +struct use_bool_op : std::false_type {}; +#endif +#endif // FU2_HAS_NO_EMPTY_PROPAGATION + template struct assert_wrong_copy_assign { - static_assert(!Config::is_copyable || + static_assert(!Config::is_owning || !Config::is_copyable || std::is_copy_constructible>::value, "Can't wrap a non copyable object into a unique function!"); @@ -1343,15 +1561,17 @@ public: function() = default; ~function() = default; - explicit constexpr function(function const& /*right*/) = default; - explicit constexpr function(function&& /*right*/) = default; + explicit FU2_DETAIL_CXX14_CONSTEXPR + function(function const& /*right*/) = default; + explicit FU2_DETAIL_CXX14_CONSTEXPR function(function&& /*right*/) = default; /// Copy construction from another copyable function template * = nullptr, enable_if_copyable_correct_t* = nullptr, enable_if_owning_correct_t* = nullptr> - constexpr function(function const& right) + FU2_DETAIL_CXX14_CONSTEXPR + function(function const& right) : erasure_(right.erasure_) { } @@ -1359,7 +1579,7 @@ public: template * = nullptr, enable_if_owning_correct_t* = nullptr> - constexpr function(function&& right) + FU2_DETAIL_CXX14_CONSTEXPR function(function&& right) : erasure_(std::move(right.erasure_)) { } @@ -1369,7 +1589,8 @@ public: enable_if_can_accept_all_t* = nullptr, assert_wrong_copy_assign_t* = nullptr, assert_no_strong_except_guarantee_t* = nullptr> - constexpr function(T&& callable) : erasure_(std::forward(callable)) { + FU2_DETAIL_CXX14_CONSTEXPR function(T&& callable) + : erasure_(use_bool_op>{}, std::forward(callable)) { } template * = nullptr, @@ -1377,13 +1598,13 @@ public: enable_if_owning_t* = nullptr, assert_wrong_copy_assign_t* = nullptr, assert_no_strong_except_guarantee_t* = nullptr> - constexpr function(T&& callable, Allocator&& allocator) - : erasure_(std::forward(callable), - std::forward(allocator)) { + FU2_DETAIL_CXX14_CONSTEXPR function(T&& callable, Allocator&& allocator_) + : erasure_(use_bool_op>{}, std::forward(callable), + std::forward(allocator_)) { } /// Empty constructs the function - constexpr function(std::nullptr_t np) : erasure_(np) { + FU2_DETAIL_CXX14_CONSTEXPR function(std::nullptr_t np) : erasure_(np) { } function& operator=(function const& /*right*/) = default; @@ -1415,7 +1636,7 @@ public: assert_wrong_copy_assign_t* = nullptr, assert_no_strong_except_guarantee_t* = nullptr> function& operator=(T&& callable) { - erasure_ = std::forward(callable); + erasure_.assign(use_bool_op>{}, std::forward(callable)); return *this; } @@ -1441,9 +1662,9 @@ public: enable_if_can_accept_all_t* = nullptr, assert_wrong_copy_assign_t* = nullptr, assert_no_strong_except_guarantee_t* = nullptr> - void assign(T&& callable, Allocator&& allocator = Allocator{}) { - erasure_.assign(std::forward(callable), - std::forward(allocator)); + void assign(T&& callable, Allocator&& allocator_ = Allocator{}) { + erasure_.assign(use_bool_op>{}, std::forward(callable), + std::forward(allocator_)); } /// Swaps this function with the given function @@ -1488,70 +1709,104 @@ bool operator!=(std::nullptr_t, function const& f) { return bool(f); } -// Default object size of the function +// Default intended object size of the function using object_size = std::integral_constant; - -// Default capacity for small functor optimization -using default_capacity = - std::integral_constant; } // namespace detail -} // namespace abi_310 - -/// Adaptable function wrapper base for arbitrary functional types. -template < - /// This is a placeholder for future non owning support - bool IsOwning, - /// Defines whether the function is copyable or not - bool IsCopyable, - /// Defines the internal capacity of the function - /// for small functor optimization. - /// The size of the whole function object will be the capacity plus - /// the size of two pointers. - /// If the capacity is zero, the size will increase through one additional - /// pointer so the whole object has the size of 3 * sizeof(void*). - std::size_t Capacity, - /// Defines whether the function throws an exception on empty function - /// call, `std::abort` is called otherwise. - bool IsThrowing, - /// Defines whether all objects satisfy the strong exception guarantees, - /// which means the function type will satisfy the strong exception - /// guarantees too. - bool HasStrongExceptGuarantee, - /// Defines the signature of the function wrapper - typename... Signatures> +} // namespace abi_400 + +/// Can be passed to function_base as template argument which causes +/// the internal small buffer to be sized according to the given size, +/// and aligned with the given alignment. +template +struct capacity_fixed { + static constexpr std::size_t capacity = Capacity; + static constexpr std::size_t alignment = Alignment; +}; + +/// Default capacity for small functor optimization +struct capacity_default + : capacity_fixed {}; + +/// Can be passed to function_base as template argument which causes +/// the internal small buffer to be removed from the callable wrapper. +/// The owning function_base will then allocate memory for every object +/// it applies a type erasure on. +struct capacity_none : capacity_fixed<0UL> {}; + +/// Can be passed to function_base as template argument which causes +/// the internal small buffer to be sized such that it can hold +/// the given object without allocating memory for an applied type erasure. +template +struct capacity_can_hold { + static constexpr std::size_t capacity = sizeof(T); + static constexpr std::size_t alignment = alignof(T); +}; + +/// An adaptable function wrapper base for arbitrary functional types. +/// +/// \tparam IsOwning Is true when the type erasure shall be owning the object. +/// +/// \tparam IsCopyable Defines whether the function is copyable or not +/// +/// \tparam Capacity Defines the internal capacity of the function +/// for small functor optimization. +/// The size of the whole function object will be the capacity +/// plus the size of two pointers. If the capacity is zero, +/// the size will increase through one additional pointer +/// so the whole object has the size of 3 * sizeof(void*). +/// The type which is passed to the Capacity template parameter +/// shall provide a capacity and alignment member which +/// looks like the following example: +/// ```cpp +/// struct my_capacity { +/// static constexpr std::size_t capacity = sizeof(my_type); +/// static constexpr std::size_t alignment = alignof(my_type); +/// }; +/// ``` +/// +/// \tparam IsThrowing Defines whether the function throws an exception on +/// empty function call, `std::abort` is called otherwise. +/// +/// \tparam HasStrongExceptGuarantee Defines whether all objects satisfy the +/// strong exception guarantees, +/// which means the function type will satisfy +/// the strong exception guarantees too. +/// +/// \tparam Signatures Defines the signature of the callable wrapper +/// +template using function_base = detail::function< detail::config, detail::property>; /// An owning copyable function wrapper for arbitrary callable types. template -using function = function_base; /// An owning non copyable function wrapper for arbitrary callable types. template -using unique_function = - function_base; +using unique_function = function_base; /// A non owning copyable function wrapper for arbitrary callable types. template -using function_view = - function_base; +using function_view = function_base; #if !defined(FU2_HAS_DISABLED_EXCEPTIONS) /// Exception type that is thrown when invoking empty function objects /// and exception support isn't disabled. /// -/// Exception suport is enabled if +/// Exception support is enabled if /// the template parameter 'Throwing' is set to true (default). /// /// This type will default to std::bad_function_call if the /// functional header is used, otherwise the library provides its own type. /// -/// You may disable the inclusion of the functionl header +/// You may disable the inclusion of the functional header /// through defining `FU2_WITH_NO_FUNCTIONAL_HEADER`. /// using detail::type_erasure::invocation_table::bad_function_call; @@ -1575,7 +1830,20 @@ constexpr auto overload(T&&... callables) { } } // namespace fu2 -#undef FU2_EXPAND_QUALIFIERS -#undef FU2_EXPAND_QUALIFIERS_NOEXCEPT +namespace std{ +template +struct uses_allocator< + ::fu2::detail::function, + Alloc +> : std::true_type {}; +} // namespace std + +#undef FU2_DETAIL_EXPAND_QUALIFIERS +#undef FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT +#undef FU2_DETAIL_EXPAND_CV +#undef FU2_DETAIL_EXPAND_CV_NOEXCEPT +#undef FU2_DETAIL_UNREACHABLE_INTRINSIC +#undef FU2_DETAIL_TRAP +#undef FU2_DETAIL_CXX14_CONSTEXPR #endif // FU2_INCLUDED_FUNCTION2_HPP_ -- 2.39.5