// 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) #ifndef FU2_INCLUDED_FUNCTION2_HPP_ #define FU2_INCLUDED_FUNCTION2_HPP_ #include #include #include #include #include #include #include // Defines: // - FU2_HAS_DISABLED_EXCEPTIONS #if defined(FU2_WITH_DISABLED_EXCEPTIONS) || \ defined(FU2_MACRO_DISABLE_EXCEPTIONS) #define FU2_HAS_DISABLED_EXCEPTIONS #else // FU2_WITH_DISABLED_EXCEPTIONS #if defined(_MSC_VER) #if !defined(_HAS_EXCEPTIONS) || (_HAS_EXCEPTIONS == 0) #define FU2_HAS_DISABLED_EXCEPTIONS #endif #elif defined(__clang__) #if !(__EXCEPTIONS && __has_feature(cxx_exceptions)) #define FU2_HAS_DISABLED_EXCEPTIONS #endif #elif defined(__GNUC__) #if !__EXCEPTIONS #define FU2_HAS_DISABLED_EXCEPTIONS #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) || \ 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) #define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE #else // FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE #if defined(_MSC_VER) #if defined(_HAS_CXX17) && _HAS_CXX17 #define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE #endif #elif defined(__cpp_noexcept_function_type) #define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE #elif defined(__cplusplus) && (__cplusplus >= 201703L) #define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE #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_400 { namespace detail { template class function; template struct identity {}; // 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 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 {}; template <> struct copyable { copyable() = default; ~copyable() = default; copyable(copyable const&) = delete; copyable(copyable&&) = default; copyable& operator=(copyable const&) = delete; copyable& operator=(copyable&&) = default; }; /// Configuration trait to configure the function_base class. template struct config { // Is true if the function is owning. static constexpr auto const is_owning = Owning; // Is true if the function is copyable. static constexpr auto const is_copyable = Copyable; // The internal capacity of the function // used in small functor optimization. // 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 template struct property { // Is true when the function throws an exception on empty invocation. 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 = 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 template constexpr auto invoke(Callable&& callable, Args&&... args) noexcept( noexcept(std::forward(callable)(std::forward(args)...))) -> decltype(std::forward(callable)(std::forward(args)...)) { return std::forward(callable)(std::forward(args)...); } /// Invokes the given member function pointer by reference 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)...)) { return (std::forward(self).*member)(std::forward(args)...); } /// Invokes the given member function pointer by pointer 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)...)) { return (std::forward(self)->*member)(std::forward(args)...); } /// Invokes the given pointer to a scalar member by reference template constexpr auto invoke(Type T::*member, Self&& self) noexcept(noexcept(std::forward(self).*member)) -> decltype(std::forward(self).*member) { return (std::forward(self).*member); } /// Invokes the given pointer to a scalar member by pointer template constexpr auto invoke(Type T::*member, Self&& self) noexcept(noexcept(std::forward(self)->*member)) -> decltype(std::forward(self)->*member) { return std::forward(self)->*member; } /// Deduces to a true type if the callable object can be invoked with /// the given arguments. /// We don't use invoke here because MSVC can't evaluate the nested expression /// SFINAE here. template struct can_invoke : std::false_type {}; template struct can_invoke, decltype((void)std::declval()(std::declval()...))> : std::true_type {}; template struct can_invoke, 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 {}; template struct can_invoke, decltype(( void)((std::declval()->*std::declval())( std::declval()...)))> : std::true_type {}; template struct can_invoke, decltype((void)(std::declval().*std::declval()))> : std::true_type {}; template struct can_invoke, decltype((void)(std::declval().* std::declval()))> : std::true_type { }; template struct can_invoke, 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()...))> { }; } // end namespace invocation namespace overloading { template struct overload_impl; 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)...) { } using Current::operator(); using overload_impl::operator(); }; template struct overload_impl : Current { explicit overload_impl(Current current) : Current(std::move(current)) { } using Current::operator(); }; template constexpr auto overload(T&&... callables) { return overload_impl...>{std::forward(callables)...}; } } // namespace overloading /// Declares the namespace which provides the functionality to work with a /// type-erased object. namespace type_erasure { /// Specialization to work with addresses of callable objects template struct address_taker { template static void* take(O&& obj) { return std::addressof(obj); } static T& restore(void* ptr) { return *static_cast(ptr); } static T const& restore(void const* ptr) { return *static_cast(ptr); } static T volatile& restore(void volatile* ptr) { return *static_cast(ptr); } static T const volatile& restore(void const volatile* ptr) { return *static_cast(ptr); } }; /// Specialization to work with addresses of raw function pointers template struct address_taker::value>> { template static void* take(O&& obj) { return reinterpret_cast(obj); } template static T restore(O ptr) { return reinterpret_cast(const_cast(ptr)); } }; template struct box_factory; /// Store the allocator inside the box template struct box : private Allocator { friend box_factory; T value_; explicit box(T value, Allocator allocator_) : Allocator(std::move(allocator_)), value_(std::move(value)) { } box(box&&) = default; box(box const&) = default; box& operator=(box&&) = default; box& operator=(box const&) = default; ~box() = default; }; template struct box : private Allocator { friend box_factory; T value_; explicit box(T value, Allocator allocator_) : Allocator(std::move(allocator_)), value_(std::move(value)) { } box(box&&) = default; box(box const&) = delete; box& operator=(box&&) = default; box& operator=(box const&) = delete; ~box() = default; }; template struct box_factory> { using real_allocator = typename std::allocator_traits>:: template rebind_alloc>; /// Allocates space through the boxed allocator static box* box_allocate(box const* me) { real_allocator allocator_(*static_cast(me)); return static_cast*>( 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)); me->~box(); std::allocator_traits::deallocate(allocator_, me, 1U); } }; /// Creates a box containing the given value and allocator template auto make_box(std::integral_constant, T&& value, Allocator&& allocator_) { return box, std::decay_t>( std::forward(value), std::forward(allocator_)); } template struct is_box : std::false_type {}; template struct is_box> : std::true_type {}; /// Provides access to the pointer to a heal allocated erased object /// as well to the inplace storage. union data_accessor { data_accessor() = default; explicit constexpr data_accessor(std::nullptr_t) noexcept : ptr_(nullptr) { } explicit constexpr data_accessor(void* ptr) noexcept : ptr_(ptr) { } /// The pointer we use if the object is on the heap void* ptr_; /// The first field of the inplace storage std::size_t inplace_storage_; }; /// See opcode::op_fetch_empty static FU2_DETAIL_CXX14_CONSTEXPR void write_empty(data_accessor* accessor, bool empty) noexcept { accessor->inplace_storage_ = std::size_t(empty); } template using transfer_const_t = std::conditional_t>::value, std::add_const_t, To>; template using transfer_volatile_t = std::conditional_t>::value, std::add_volatile_t, To>; /// The retriever when the object is allocated inplace template 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 auto storage = &(from->inplace_storage_); auto inplace = const_cast(static_cast(storage)); return type(std::align(alignof(T), sizeof(T), inplace, from_capacity)); } /// The retriever which is used when the object is allocated /// through the allocator template constexpr auto retrieve(std::false_type /*is_inplace*/, Accessor from, std::size_t /*from_capacity*/) { return from->ptr_; } namespace invocation_table { #if !defined(FU2_HAS_DISABLED_EXCEPTIONS) #if defined(FU2_HAS_NO_FUNCTIONAL_HEADER) struct bad_function_call : std::exception { bad_function_call() noexcept { } char const* what() const noexcept override { return "bad function call"; } }; #else using std::bad_function_call; #endif #endif #ifdef FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE #define FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F) \ F(, , noexcept, , &) \ F(const, , noexcept, , &) \ F(, volatile, noexcept, , &) \ F(const, volatile, noexcept, , &) \ F(, , noexcept, &, &) \ F(const, , noexcept, &, &) \ F(, volatile, noexcept, &, &) \ F(const, volatile, noexcept, &, &) \ F(, , noexcept, &&, &&) \ 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_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F) #define FU2_DETAIL_EXPAND_CV_NOEXCEPT(F) #endif // FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE #define FU2_DETAIL_EXPAND_QUALIFIERS(F) \ F(, , , , &) \ F(const, , , , &) \ F(, volatile, , , &) \ F(const, volatile, , , &) \ F(, , , &, &) \ F(const, , , &, &) \ F(, volatile, , &, &) \ F(const, volatile, , &, &) \ F(, , , &&, &&) \ F(const, , , &&, &&) \ F(, volatile, , &&, &&) \ F(const, volatile, , &&, &&) \ 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 [[noreturn]] void throw_or_abortnoexcept( std::integral_constant /*is_throwing*/) noexcept { std::abort(); } /// Calls std::abort on empty function calls [[noreturn]] inline void throw_or_abort(std::false_type /*is_throwing*/) noexcept { std::abort(); } /// Throws bad_function_call on empty funciton calls [[noreturn]] inline void throw_or_abort(std::true_type /*is_throwing*/) { #ifdef FU2_HAS_DISABLED_EXCEPTIONS throw_or_abort(std::false_type{}); #else throw bad_function_call{}; #endif } template struct function_trait; using is_noexcept_ = std::false_type; using is_noexcept_noexcept = std::true_type; #define FU2_DEFINE_FUNCTION_TRAIT(CONST, VOLATILE, NOEXCEPT, OVL_REF, REF) \ template \ struct function_trait { \ using pointer_type = Ret (*)(data_accessor CONST VOLATILE*, \ std::size_t capacity, Args...); \ template \ struct internal_invoker { \ static Ret invoke(data_accessor CONST VOLATILE* data, \ std::size_t capacity, Args... args) NOEXCEPT { \ auto obj = retrieve(std::integral_constant{}, \ data, capacity); \ auto box = static_cast(obj); \ return invocation::invoke( \ static_castvalue_)> CONST VOLATILE \ REF>(box->value_), \ std::forward(args)...); \ } \ }; \ \ template \ struct view_invoker { \ static Ret invoke(data_accessor CONST VOLATILE* data, std::size_t, \ Args... args) NOEXCEPT { \ \ auto ptr = static_cast(data->ptr_); \ return invocation::invoke(address_taker::restore(ptr), \ std::forward(args)...); \ } \ }; \ \ template \ using callable = T CONST VOLATILE REF; \ \ using arguments = identity; \ \ using is_noexcept = is_noexcept_##NOEXCEPT; \ \ template \ struct empty_invoker { \ static Ret invoke(data_accessor CONST VOLATILE* /*data*/, \ std::size_t /*capacity*/, Args... /*args*/) NOEXCEPT { \ throw_or_abort##NOEXCEPT(std::integral_constant{}); \ } \ }; \ }; FU2_DETAIL_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT) #undef FU2_DEFINE_FUNCTION_TRAIT /// Deduces to the function pointer to the given signature template using function_pointer_of = typename function_trait::pointer_type; template struct invoke_table; /// We optimize the vtable_t in case there is a single function overload template struct invoke_table { using type = function_pointer_of; /// Return the function pointer itself template static constexpr auto fetch(type pointer) noexcept { static_assert(Index == 0U, "The index should be 0 here!"); return pointer; } /// Returns the thunk of an single overloaded callable template static constexpr type get_invocation_table_of() noexcept { return &function_trait::template internal_invoker::invoke; } /// Returns the thunk of an single overloaded callable template static constexpr type get_invocation_view_table_of() noexcept { return &function_trait::template view_invoker::invoke; } /// Returns the thunk of an empty single overloaded callable template static constexpr type get_empty_invocation_table() noexcept { return &function_trait::template empty_invoker::invoke; } }; /// We generate a table in case of multiple function overloads template struct invoke_table { using type = std::tuple, function_pointer_of, function_pointer_of...> const*; /// Return the function pointer at the particular index template static constexpr auto fetch(type table) noexcept { return std::get(*table); } /// The invocation vtable for a present object template struct invocation_vtable : public std::tuple, function_pointer_of, function_pointer_of...> { constexpr invocation_vtable() noexcept : std::tuple, function_pointer_of, function_pointer_of...>(std::make_tuple( &function_trait::template internal_invoker< T, IsInplace>::invoke, &function_trait::template internal_invoker< T, IsInplace>::invoke, &function_trait::template internal_invoker< T, IsInplace>::invoke...)) { } }; /// Returns the thunk of an multi overloaded callable template static type get_invocation_table_of() noexcept { static invocation_vtable const table; return &table; } /// The invocation vtable for a present object template struct invocation_view_vtable : public std::tuple, function_pointer_of, function_pointer_of...> { constexpr invocation_view_vtable() noexcept : std::tuple, function_pointer_of, function_pointer_of...>(std::make_tuple( &function_trait::template view_invoker::invoke, &function_trait::template view_invoker::invoke, &function_trait::template view_invoker::invoke...)) { } }; /// Returns the thunk of an multi overloaded callable template static type get_invocation_view_table_of() noexcept { static invocation_view_vtable const table; return &table; } /// The invocation table for an empty wrapper template struct empty_vtable : public std::tuple, function_pointer_of, function_pointer_of...> { constexpr empty_vtable() noexcept : std::tuple, function_pointer_of, function_pointer_of...>( std::make_tuple(&function_trait::template empty_invoker< IsThrowing>::invoke, &function_trait::template empty_invoker< IsThrowing>::invoke, &function_trait::template empty_invoker< IsThrowing>::invoke...)) { } }; /// Returns the thunk of an multi single overloaded callable template static type get_empty_invocation_table() noexcept { static empty_vtable const table; return &table; } }; template class operator_impl; #define FU2_DEFINE_FUNCTION_TRAIT(CONST, VOLATILE, NOEXCEPT, OVL_REF, REF) \ template \ class operator_impl \ : operator_impl { \ \ template \ friend class operator_impl; \ \ protected: \ operator_impl() = default; \ ~operator_impl() = default; \ operator_impl(operator_impl const&) = default; \ operator_impl(operator_impl&&) = default; \ operator_impl& operator=(operator_impl const&) = default; \ operator_impl& operator=(operator_impl&&) = default; \ \ using operator_impl::operator(); \ \ Ret operator()(Args... args) CONST VOLATILE OVL_REF NOEXCEPT { \ auto parent = static_cast(this); \ using erasure_t = std::decay_terasure_)>; \ \ /* `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)...); \ } \ }; \ template \ class operator_impl, \ Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT> \ : copyable { \ \ template \ friend class operator_impl; \ \ protected: \ operator_impl() = default; \ ~operator_impl() = default; \ operator_impl(operator_impl const&) = default; \ operator_impl(operator_impl&&) = default; \ operator_impl& operator=(operator_impl const&) = default; \ operator_impl& operator=(operator_impl&&) = default; \ \ Ret operator()(Args... args) CONST VOLATILE OVL_REF NOEXCEPT { \ auto parent = \ static_cast CONST VOLATILE*>(this); \ using erasure_t = std::decay_terasure_)>; \ \ /* `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_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 }; /// Abstraction for a vtable together with a command table /// TODO Add optimization for a single formal argument /// TODO Add optimization to merge both tables if the function is size /// optimized template class vtable; template class vtable> { using command_function_t = void (*)(vtable* /*this*/, opcode /*op*/, data_accessor* /*from*/, std::size_t /*from_capacity*/, data_accessor* /*to*/, std::size_t /*to_capacity*/); using invoke_table_t = invocation_table::invoke_table; command_function_t cmd_; typename invoke_table_t::type vtable_; template struct trait { static_assert(is_box::value, "The trait must be specialized with a box!"); /// The command table template static void process_cmd(vtable* to_table, opcode op, data_accessor* from, std::size_t from_capacity, data_accessor* to, std::size_t to_capacity) { switch (op) { case opcode::op_move: { /// Retrieve the pointer to the object auto box = static_cast(retrieve( std::integral_constant{}, from, from_capacity)); assert(box && "The object must not be over aligned or null!"); if (!IsInplace) { // Just swap both pointers if we allocated on the heap to->ptr_ = from->ptr_; #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. from->ptr_ = nullptr; #endif to_table->template set_allocated(); } // The object is allocated inplace else { construct(std::true_type{}, std::move(*box), to_table, to, to_capacity); box->~T(); } return; } case opcode::op_copy: { auto box = static_cast(retrieve( std::integral_constant{}, from, from_capacity)); assert(box && "The object must not be over aligned or null!"); assert(std::is_copy_constructible::value && "The box is required to be copyable here!"); // Try to allocate the object inplace construct(std::is_copy_constructible{}, *box, to_table, to, to_capacity); return; } case opcode::op_destroy: case opcode::op_weak_destroy: { assert(!to && !to_capacity && "Arg overflow!"); auto box = static_cast(retrieve( std::integral_constant{}, from, from_capacity)); if (IsInplace) { box->~T(); } else { box_factory::box_deallocate(box); } if (op == opcode::op_destroy) { to_table->set_empty(); } return; } case opcode::op_fetch_empty: { write_empty(to, false); return; } } FU2_DETAIL_UNREACHABLE(); } template static void construct(std::true_type /*apply*/, Box&& box, vtable* to_table, data_accessor* to, std::size_t to_capacity) noexcept(HasStrongExceptGuarantee) { // Try to allocate the object inplace void* storage = retrieve(std::true_type{}, to, to_capacity); if (storage) { to_table->template set_inplace(); } else { // Allocate the object through the allocator to->ptr_ = storage = box_factory>::box_allocate(std::addressof(box)); to_table->template set_allocated(); } new (storage) T(std::forward(box)); } template static void construct(std::false_type /*apply*/, Box&& /*box*/, vtable* /*to_table*/, data_accessor* /*to*/, std::size_t /*to_capacity*/) noexcept(HasStrongExceptGuarantee) { } }; /// The command table static void empty_cmd(vtable* to_table, opcode op, data_accessor* /*from*/, std::size_t /*from_capacity*/, data_accessor* to, std::size_t /*to_capacity*/) { switch (op) { case opcode::op_move: case opcode::op_copy: { to_table->set_empty(); break; } case opcode::op_destroy: case opcode::op_weak_destroy: { // Do nothing break; } case opcode::op_fetch_empty: { write_empty(to, true); break; } default: { FU2_DETAIL_UNREACHABLE(); } } } public: vtable() noexcept = default; /// Initialize an object at the given position template static void init(vtable& table, T&& object, data_accessor* to, std::size_t to_capacity) { trait>::construct(std::true_type{}, std::forward(object), &table, to, to_capacity); } /// Moves the object at the given position void move(vtable& to_table, data_accessor* from, std::size_t from_capacity, data_accessor* to, std::size_t to_capacity) noexcept(HasStrongExceptGuarantee) { cmd_(&to_table, opcode::op_move, from, from_capacity, to, to_capacity); set_empty(); } /// Destroys the object at the given position void copy(vtable& to_table, data_accessor const* from, std::size_t from_capacity, data_accessor* to, std::size_t to_capacity) const { cmd_(&to_table, opcode::op_copy, const_cast(from), from_capacity, to, to_capacity); } /// Destroys the object at the given position void destroy(data_accessor* from, std::size_t from_capacity) noexcept(HasStrongExceptGuarantee) { cmd_(this, opcode::op_destroy, from, from_capacity, nullptr, 0U); } /// Destroys the object at the given position without invalidating the /// vtable void weak_destroy(data_accessor* from, std::size_t from_capacity) noexcept(HasStrongExceptGuarantee) { cmd_(this, opcode::op_weak_destroy, from, from_capacity, nullptr, 0U); } /// Returns true when the vtable doesn't hold any erased object bool empty() const noexcept { data_accessor data; cmd_(nullptr, opcode::op_fetch_empty, nullptr, 0U, &data, 0U); return bool(data.inplace_storage_); } /// Invoke the function at the given index template 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 decltype(auto) invoke(Args&&... args) const volatile { auto thunk = invoke_table_t::template fetch(vtable_); return thunk(std::forward(args)...); } template void set_inplace() noexcept { using type = std::decay_t; vtable_ = invoke_table_t::template get_invocation_table_of(); cmd_ = &trait::template process_cmd; } template void set_allocated() noexcept { using type = std::decay_t; vtable_ = invoke_table_t::template get_invocation_table_of(); cmd_ = &trait::template process_cmd; } void set_empty() noexcept { vtable_ = invoke_table_t::template get_empty_invocation_table(); cmd_ = &empty_cmd; } }; } // namespace tables /// A union which makes the pointer to the heap object share the /// same space with the internal capacity. /// The storage type is distinguished by multiple versions of the /// control and vtable. 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_; } type; }; 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 class internal_capacity_holder { // Tag to access the structure in a type-safe way typename internal_capacity::type storage_; public: constexpr internal_capacity_holder() = default; FU2_DETAIL_CXX14_CONSTEXPR data_accessor* opaque_ptr() noexcept { return &storage_.accessor_; } constexpr data_accessor const* opaque_ptr() const noexcept { return &storage_.accessor_; } FU2_DETAIL_CXX14_CONSTEXPR data_accessor volatile* opaque_ptr() volatile noexcept { return &storage_.accessor_; } constexpr data_accessor const volatile* opaque_ptr() const volatile noexcept { return &storage_.accessor_; } static constexpr std::size_t capacity() noexcept { return sizeof(storage_); } }; /// An owning erasure template class erasure : internal_capacity_holder { template friend class erasure; template friend class operator_impl; using vtable_t = tables::vtable; vtable_t vtable_; public: /// Returns the capacity of this erasure static constexpr std::size_t capacity() noexcept { return internal_capacity_holder::capacity(); } FU2_DETAIL_CXX14_CONSTEXPR erasure() noexcept { vtable_.set_empty(); } FU2_DETAIL_CXX14_CONSTEXPR erasure(std::nullptr_t) noexcept { vtable_.set_empty(); } 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()); } FU2_DETAIL_CXX14_CONSTEXPR erasure(erasure const& right) { right.vtable_.copy(vtable_, right.opaque_ptr(), right.capacity(), this->opaque_ptr(), capacity()); } template 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 >> 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_)), 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()); } FU2_DETAIL_CXX14_CONSTEXPR erasure& operator=(std::nullptr_t) noexcept(Property::is_strong_exception_guaranteed) { vtable_.destroy(this->opaque_ptr(), capacity()); return *this; } 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(), this->opaque_ptr(), capacity()); return *this; } 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()); return *this; } template 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(), this->opaque_ptr(), capacity()); return *this; } 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(allocator_)), 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(); } /// Invoke the function of the erasure at the given index /// /// We define this out of class to be able to forward the qualified /// erasure correctly. template 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, std::forward(args)...); } }; // A non owning erasure template class erasure> { template friend class erasure; template friend class operator_impl; using property_t = property; using invoke_table_t = invocation_table::invoke_table; typename invoke_table_t::type invoke_table_; /// The internal pointer to the non owned object data_accessor view_; public: // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) constexpr erasure() noexcept : invoke_table_( invoke_table_t::template get_empty_invocation_table()), view_(nullptr) { } // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) constexpr erasure(std::nullptr_t) noexcept : invoke_table_( invoke_table_t::template get_empty_invocation_table()), view_(nullptr) { } // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) constexpr erasure(erasure&& right) noexcept : invoke_table_(right.invoke_table_), view_(right.view_) { } constexpr erasure(erasure const& /*right*/) = default; template // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) constexpr erasure(erasure right) noexcept : invoke_table_(right.invoke_table_), view_(right.view_) { } template // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) 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; constexpr erasure& operator=(std::nullptr_t) noexcept(HasStrongExceptGuarantee) { invoke_table_ = invoke_table_t::template get_empty_invocation_table(); view_.ptr_ = nullptr; return *this; } constexpr erasure& operator=(erasure&& right) noexcept { invoke_table_ = right.invoke_table_; view_ = right.view_; right = nullptr; return *this; } constexpr erasure& operator=(erasure const& /*right*/) = default; template constexpr erasure& operator=(erasure right) noexcept { invoke_table_ = right.invoke_table_; view_ = right.view_; return *this; } template 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(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 constexpr bool empty() const noexcept { return view_.ptr_ == nullptr; } template 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)...); } }; } // namespace type_erasure /// Deduces to a true_type if the type T provides the given signature and the /// signature is noexcept correct callable. template > struct accepts_one : 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 struct accepts_all : std::false_type {}; template struct accepts_all< T, identity, 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_owning || !Config::is_copyable || std::is_copy_constructible>::value, "Can't wrap a non copyable object into a unique function!"); using type = void; }; template struct assert_no_strong_except_guarantee { static_assert( !IsStrongExceptGuaranteed || (std::is_nothrow_move_constructible::value && std::is_nothrow_destructible::value), "Can't wrap a object an object that has no strong exception guarantees " "if this is required by the wrapper!"); using type = void; }; /// SFINAES out if the given callable is not copyable correct to the left one. template using enable_if_copyable_correct_t = std::enable_if_t<(!LeftConfig::is_copyable || RightConfig::is_copyable)>; template using is_owning_correct = std::integral_constant; /// SFINAES out if the given function2 is not owning correct to this one template using enable_if_owning_correct_t = std::enable_if_t::value>; template class function> : type_erasure::invocation_table::operator_impl< 0U, function>, Args...> { template friend class function; template friend class type_erasure::invocation_table::operator_impl; using property_t = property; using erasure_t = type_erasure::erasure; template using enable_if_can_accept_all_t = std::enable_if_t, identity>::value>; template struct is_convertible_to_this : std::false_type {}; template struct is_convertible_to_this< function, void_t, enable_if_owning_correct_t>> : std::true_type {}; template using enable_if_not_convertible_to_this = std::enable_if_t>::value>; template using enable_if_owning_t = std::enable_if_t::value && Config::is_owning>; template using assert_wrong_copy_assign_t = typename assert_wrong_copy_assign>::type; template using assert_no_strong_except_guarantee_t = typename assert_no_strong_except_guarantee>::type; erasure_t erasure_; public: /// Default constructor which empty constructs the function function() = default; ~function() = 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> FU2_DETAIL_CXX14_CONSTEXPR function(function const& right) : erasure_(right.erasure_) { } /// Move construction from another function template * = nullptr, enable_if_owning_correct_t* = nullptr> FU2_DETAIL_CXX14_CONSTEXPR function(function&& right) : erasure_(std::move(right.erasure_)) { } /// Construction from a callable object which overloads the `()` operator template * = nullptr, enable_if_can_accept_all_t* = nullptr, assert_wrong_copy_assign_t* = nullptr, assert_no_strong_except_guarantee_t* = nullptr> FU2_DETAIL_CXX14_CONSTEXPR function(T&& callable) : erasure_(use_bool_op>{}, std::forward(callable)) { } template * = nullptr, enable_if_can_accept_all_t* = nullptr, enable_if_owning_t* = nullptr, assert_wrong_copy_assign_t* = nullptr, assert_no_strong_except_guarantee_t* = nullptr> FU2_DETAIL_CXX14_CONSTEXPR function(T&& callable, Allocator&& allocator_) : erasure_(use_bool_op>{}, std::forward(callable), std::forward(allocator_)) { } /// Empty constructs the function FU2_DETAIL_CXX14_CONSTEXPR function(std::nullptr_t np) : erasure_(np) { } function& operator=(function const& /*right*/) = default; function& operator=(function&& /*right*/) = default; /// Copy assigning from another copyable function template * = nullptr, enable_if_copyable_correct_t* = nullptr, enable_if_owning_correct_t* = nullptr> function& operator=(function const& right) { erasure_ = right.erasure_; return *this; } /// Move assigning from another function template * = nullptr, enable_if_owning_correct_t* = nullptr> function& operator=(function&& right) { erasure_ = std::move(right.erasure_); return *this; } /// Move assigning from a callable object template * = nullptr, enable_if_can_accept_all_t* = nullptr, assert_wrong_copy_assign_t* = nullptr, assert_no_strong_except_guarantee_t* = nullptr> function& operator=(T&& callable) { erasure_.assign(use_bool_op>{}, std::forward(callable)); return *this; } /// Clears the function function& operator=(std::nullptr_t np) { erasure_ = np; return *this; } /// Returns true when the function is empty bool empty() const noexcept { return erasure_.empty(); } /// Returns true when the function isn't empty explicit operator bool() const noexcept { return !empty(); } /// Assigns a new target with an optional allocator template >, enable_if_not_convertible_to_this* = nullptr, 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(use_bool_op>{}, std::forward(callable), std::forward(allocator_)); } /// Swaps this function with the given function void swap(function& other) noexcept(HasStrongExceptGuarantee) { if (&other == this) { return; } function cache = std::move(other); other = std::move(*this); *this = std::move(cache); } /// Swaps the left function with the right one friend void swap(function& left, function& right) noexcept(HasStrongExceptGuarantee) { left.swap(right); } /// Calls the wrapped callable object using type_erasure::invocation_table::operator_impl< 0U, function, Args...>::operator(); }; template bool operator==(function const& f, std::nullptr_t) { return !bool(f); } template bool operator!=(function const& f, std::nullptr_t) { return bool(f); } template bool operator==(std::nullptr_t, function const& f) { return !bool(f); } template bool operator!=(std::nullptr_t, function const& f) { return bool(f); } // Default intended object size of the function using object_size = std::integral_constant; } // namespace detail } // 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; /// A non owning copyable function wrapper for arbitrary callable types. template 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 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 functional header /// through defining `FU2_WITH_NO_FUNCTIONAL_HEADER`. /// using detail::type_erasure::invocation_table::bad_function_call; #endif /// Returns a callable object, which unifies all callable objects /// that were passed to this function. /// /// ```cpp /// auto overloaded = fu2::overload([](std::true_type) { return true; }, /// [](std::false_type) { return false; }); /// ``` /// /// \param callables A pack of callable objects with arbitrary signatures. /// /// \returns A callable object which exposes the /// template constexpr auto overload(T&&... callables) { return detail::overloading::overload(std::forward(callables)...); } } // namespace fu2 #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_