diff --git a/src/f2/function2.hpp b/src/f2/function2.hpp index 4d18db0..dc81c7d 100644 --- a/src/f2/function2.hpp +++ b/src/f2/function2.hpp @@ -36,11 +36,12 @@ #endif #endif // FU2_WITH_DISABLED_EXCEPTIONS // - FU2_HAS_NO_FUNCTIONAL_HEADER -#if !defined(FU2_WITH_NO_FUNCTIONAL_HEADER) || \ - !defined(FU2_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 #include +#else +#define FU2_HAS_NO_FUNCTIONAL_HEADER #endif // - FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE #if defined(FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE) @@ -57,10 +58,53 @@ #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 +/// 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) && __has_builtin(__builtin_unreachable) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable() +#else +// 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) && __has_builtin(__builtin_trap) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_TRAP() __builtin_trap() +#else +// 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_400 { namespace detail { @@ -70,7 +114,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 @@ -79,6 +123,9 @@ struct deduce_to_void : std::common_type {}; template using void_t = typename deduce_to_void::type; +template +using unrefcv_t = std::remove_cv_t>; + // Copy enabler helper class template struct copyable {}; @@ -115,9 +162,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 @@ -191,9 +246,9 @@ struct can_invoke, : std::true_type {}; template struct can_invoke, - decltype((void)(std::declval().* - std::declval()))> : std::true_type { -}; + decltype( + (void)(std::declval().*std::declval()))> + : std::true_type {}; template struct can_invoke, decltype( @@ -334,12 +389,11 @@ struct box_factory> { }; /// 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 @@ -408,13 +462,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, , &) \ @@ -427,11 +481,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, , , &) \ @@ -444,7 +504,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 @@ -518,7 +584,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 @@ -669,7 +735,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)...); \ } \ @@ -678,7 +746,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; \ @@ -696,13 +764,15 @@ 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 @@ -759,7 +829,7 @@ class vtable> { // Just swap both pointers if we allocated on the heap to->ptr_ = from->ptr_; -#ifndef _NDEBUG +#ifndef NDEBUG // We don't need to null the pointer since we know that // we don't own the data anymore through the vtable // which is set to empty. @@ -814,9 +884,7 @@ class vtable> { } } - // TODO Use an unreachable intrinsic - assert(false && "Unreachable!"); - std::exit(-1); + FU2_DETAIL_UNREACHABLE(); } template @@ -865,6 +933,9 @@ class vtable> { write_empty(to, true); break; } + default: { + FU2_DETAIL_UNREACHABLE(); + } } } @@ -1045,7 +1116,8 @@ public: } template >> - constexpr erasure(T&& callable, Allocator&& allocator = Allocator{}) { + constexpr erasure(std::false_type /*use_bool_op*/, T&& callable, + Allocator&& allocator = Allocator{}) { vtable_t::init(vtable_, type_erasure::make_box( std::integral_constant{}, @@ -1053,6 +1125,20 @@ public: std::forward(allocator)), this->opaque_ptr(), capacity()); } + template >> + constexpr erasure(std::true_type /*use_bool_op*/, T&& callable, + Allocator&& allocator = Allocator{}) { + if (bool(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()); @@ -1089,19 +1175,9 @@ public: return *this; } - template - constexpr erasure& operator=(T&& callable) { - vtable_.weak_destroy(this->opaque_ptr(), capacity()); - vtable_t::init(vtable_, - type_erasure::make_box( - std::integral_constant{}, - std::forward(callable)), - this->opaque_ptr(), capacity()); - return *this; - } - - template - void assign(T&& callable, Allocator&& allocator) { + 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( @@ -1111,6 +1187,17 @@ public: this->opaque_ptr(), capacity()); } + template >> + void assign(std::true_type /*use_bool_op*/, T&& callable, + Allocator&& allocator = {}) { + if (bool(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 constexpr bool empty() const noexcept { return vtable_.empty(); @@ -1177,11 +1264,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) + constexpr erasure(std::true_type use_bool_op, T&& object) { + this->assign(use_bool_op, std::forward(object)); + } ~erasure() = default; @@ -1211,11 +1303,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_.ptr_ = + address_taker>::take(std::forward(callable)); + } + template + constexpr void assign(std::true_type /*use_bool_op*/, T&& callable) { + if (bool(callable)) { + assign(std::false_type{}, std::forward(callable)); + } else { + operator=(nullptr); + } } /// Returns true when the erasure doesn't hold any erased object @@ -1254,9 +1354,48 @@ struct accepts_all< void_t::value>...>> : std::true_type {}; +/// 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. +#if defined(FU2_HAS_NO_EMPTY_PROPAGATION) +template +struct use_bool_op : std::false_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 +}; + +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!"); @@ -1372,7 +1511,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)) { + constexpr function(T&& callable) + : erasure_(use_bool_op>{}, std::forward(callable)) { } template * = nullptr, @@ -1381,7 +1521,7 @@ public: assert_wrong_copy_assign_t* = nullptr, assert_no_strong_except_guarantee_t* = nullptr> constexpr function(T&& callable, Allocator&& allocator) - : erasure_(std::forward(callable), + : erasure_(use_bool_op>{}, std::forward(callable), std::forward(allocator)) { } @@ -1418,7 +1558,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; } @@ -1445,7 +1585,7 @@ public: 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), + erasure_.assign(use_bool_op>{}, std::forward(callable), std::forward(allocator)); } @@ -1582,13 +1722,13 @@ using function_view = function_base