From: Vsevolod Stakhov Date: Mon, 29 Mar 2021 13:23:09 +0000 (+0100) Subject: [Project] Add fu2 library to better functions abstractions X-Git-Tag: 3.0~545 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=7f296c74ccf9cc73ea141c99c1a6ca7840e2b5d3;p=rspamd.git [Project] Add fu2 library to better functions abstractions --- diff --git a/CMakeLists.txt b/CMakeLists.txt index a50bba36f..2a42d1886 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,6 +119,7 @@ INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/" "${CMAKE_SOURCE_DIR}/contrib/frozen/include" "${CMAKE_SOURCE_DIR}/contrib/fmt/include" "${CMAKE_SOURCE_DIR}/contrib/doctest" + "${CMAKE_BINARY_DIR}/contrib/fu2/include" "${CMAKE_BINARY_DIR}/src" #Stored in the binary dir "${CMAKE_BINARY_DIR}/src/libcryptobox") diff --git a/contrib/DEPENDENCY_INFO.md b/contrib/DEPENDENCY_INFO.md index 9f6a156f9..f64556d33 100644 --- a/contrib/DEPENDENCY_INFO.md +++ b/contrib/DEPENDENCY_INFO.md @@ -33,3 +33,4 @@ | frozen | 1.0.1 | Apache 2 | NO | | | fmt | 7.1.3 | MIT | NO | | | doctest | 2.4.5 | MIT | NO | | +| function2 | 4.1.0 | Boost | NO | | \ No newline at end of file diff --git a/contrib/fu2/LICENSE.txt b/contrib/fu2/LICENSE.txt new file mode 100644 index 000000000..36b7cd93c --- /dev/null +++ b/contrib/fu2/LICENSE.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/contrib/fu2/include/function2/function2.hpp b/contrib/fu2/include/function2/function2.hpp new file mode 100644 index 000000000..a45cb580f --- /dev/null +++ b/contrib/fu2/include/function2/function2.hpp @@ -0,0 +1,1792 @@ + +// 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_NO_FUNCTIONAL_HEADER +#if !defined(FU2_WITH_NO_FUNCTIONAL_HEADER) && \ + !defined(FU2_NO_FUNCTIONAL_HEADER) && \ + !defined(FU2_HAS_DISABLED_EXCEPTIONS) +#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>; + +// 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 (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()); + } + + 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 + : 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> {}; + +/// 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 {}; + +/// 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_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_