
//  Copyright 2015-2020 Denis Blank <denis.blank at outlook dot com>
//     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 <cassert>
#include <cstddef>
#include <cstdlib>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>

// 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 <functional>
#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 <exception>
#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 <typename Config, typename Property>
class function;

template <typename...>
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 <typename...>
struct deduce_to_void : std::common_type<void> {};

template <typename... T>
using void_t = typename deduce_to_void<T...>::type;

template <typename T>
using unrefcv_t = std::remove_cv_t<std::remove_reference_t<T>>;

template <typename...>
struct lazy_and;

template <typename B1>
struct lazy_and<B1> : B1 {};

template <typename B1, typename B2>
struct lazy_and<B1, B2> : std::conditional<B1::value, B2, B1>::type {};

// template <typename B1, typename B2, typename B3, typename... Bn>
// struct lazy_and<B1, B2, B3, Bn...>
//     : std::conditional<B1::value, lazy_and<B2, B3, Bn...>, B1>::type {};

// Copy enabler helper class
template <bool /*Copyable*/>
struct copyable {};
template <>
struct copyable<false> {
  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 <bool Owning, bool Copyable, typename Capacity>
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 <bool Throws, bool HasStrongExceptGuarantee, typename... Args>
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 <typename Callable, typename... Args>
constexpr auto invoke(Callable&& callable, Args&&... args) noexcept(
    noexcept(std::forward<Callable>(callable)(std::forward<Args>(args)...)))
    -> decltype(std::forward<Callable>(callable)(std::forward<Args>(args)...)) {

  return std::forward<Callable>(callable)(std::forward<Args>(args)...);
}
/// Invokes the given member function pointer by reference
template <typename T, typename Type, typename Self, typename... Args>
constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept(
    noexcept((std::forward<Self>(self).*member)(std::forward<Args>(args)...)))
    -> decltype((std::forward<Self>(self).*
                 member)(std::forward<Args>(args)...)) {
  return (std::forward<Self>(self).*member)(std::forward<Args>(args)...);
}
/// Invokes the given member function pointer by pointer
template <typename T, typename Type, typename Self, typename... Args>
constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept(
    noexcept((std::forward<Self>(self)->*member)(std::forward<Args>(args)...)))
    -> decltype((std::forward<Self>(self)->*member)(
        std::forward<Args>(args)...)) {
  return (std::forward<Self>(self)->*member)(std::forward<Args>(args)...);
}
/// Invokes the given pointer to a scalar member by reference
template <typename T, typename Type, typename Self>
constexpr auto
invoke(Type T::*member,
       Self&& self) noexcept(noexcept(std::forward<Self>(self).*member))
    -> decltype(std::forward<Self>(self).*member) {
  return (std::forward<Self>(self).*member);
}
/// Invokes the given pointer to a scalar member by pointer
template <typename T, typename Type, typename Self>
constexpr auto
invoke(Type T::*member,
       Self&& self) noexcept(noexcept(std::forward<Self>(self)->*member))
    -> decltype(std::forward<Self>(self)->*member) {
  return std::forward<Self>(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 <typename T, typename Args, typename = void>
struct can_invoke : std::false_type {};
template <typename T, typename... Args>
struct can_invoke<T, identity<Args...>,
                  decltype((void)std::declval<T>()(std::declval<Args>()...))>
    : std::true_type {};
template <typename Pointer, typename T, typename... Args>
struct can_invoke<Pointer, identity<T&, Args...>,
                  decltype((void)((std::declval<T&>().*std::declval<Pointer>())(
                      std::declval<Args>()...)))> : std::true_type {};
template <typename Pointer, typename T, typename... Args>
struct can_invoke<Pointer, identity<T&&, Args...>,
                  decltype((
                      void)((std::declval<T&&>().*std::declval<Pointer>())(
                      std::declval<Args>()...)))> : std::true_type {};
template <typename Pointer, typename T, typename... Args>
struct can_invoke<Pointer, identity<T*, Args...>,
                  decltype((
                      void)((std::declval<T*>()->*std::declval<Pointer>())(
                      std::declval<Args>()...)))> : std::true_type {};
template <typename Pointer, typename T>
struct can_invoke<Pointer, identity<T&>,
                  decltype((void)(std::declval<T&>().*std::declval<Pointer>()))>
    : std::true_type {};
template <typename Pointer, typename T>
struct can_invoke<Pointer, identity<T&&>,
                  decltype((void)(std::declval<T&&>().*
                                  std::declval<Pointer>()))> : std::true_type {
};
template <typename Pointer, typename T>
struct can_invoke<Pointer, identity<T*>,
                  decltype((
                      void)(std::declval<T*>()->*std::declval<Pointer>()))>
    : std::true_type {};

template <bool RequiresNoexcept, typename T, typename Args>
struct is_noexcept_correct : std::true_type {};
template <typename T, typename... Args>
struct is_noexcept_correct<true, T, identity<Args...>>
    : std::integral_constant<bool,
                             noexcept(::fu2::detail::invocation::invoke(
                                 std::declval<T>(), std::declval<Args>()...))> {
};
} // end namespace invocation

namespace overloading {
template <typename... Args>
struct overload_impl;
template <typename Current, typename Next, typename... Rest>
struct overload_impl<Current, Next, Rest...> : Current,
                                               overload_impl<Next, Rest...> {
  explicit overload_impl(Current current, Next next, Rest... rest)
      : Current(std::move(current)),
        overload_impl<Next, Rest...>(std::move(next), std::move(rest)...) {
  }

  using Current::operator();
  using overload_impl<Next, Rest...>::operator();
};
template <typename Current>
struct overload_impl<Current> : Current {
  explicit overload_impl(Current current) : Current(std::move(current)) {
  }

  using Current::operator();
};

template <typename... T>
constexpr auto overload(T&&... callables) {
  return overload_impl<std::decay_t<T>...>{std::forward<T>(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 <typename T, typename = void>
struct address_taker {
  template <typename O>
  static auto take(O&& obj) {
    return std::addressof(obj);
  }
  static T& restore(void* ptr) {
    return *static_cast<T*>(ptr);
  }
  static T const& restore(void const* ptr) {
    return *static_cast<T const*>(ptr);
  }
  static T volatile& restore(void volatile* ptr) {
    return *static_cast<T volatile*>(ptr);
  }
  static T const volatile& restore(void const volatile* ptr) {
    return *static_cast<T const volatile*>(ptr);
  }
};
/// Specialization to work with addresses of raw function pointers
template <typename T>
struct address_taker<T, std::enable_if_t<std::is_pointer<T>::value>> {
  template <typename O>
  static void* take(O&& obj) {
    return reinterpret_cast<void*>(obj);
  }
  template <typename O>
  static T restore(O ptr) {
    return reinterpret_cast<T>(const_cast<void*>(ptr));
  }
};

template <typename Box>
struct box_factory;
/// Store the allocator inside the box
template <bool IsCopyable, typename T, typename Allocator>
struct box : private Allocator {
  friend box_factory<box>;

  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 <typename T, typename Allocator>
struct box<false, T, Allocator> : private Allocator {
  friend box_factory<box>;

  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 <bool IsCopyable, typename T, typename Allocator>
struct box_factory<box<IsCopyable, T, Allocator>> {
  using real_allocator =
      typename std::allocator_traits<std::decay_t<Allocator>>::
          template rebind_alloc<box<IsCopyable, T, Allocator>>;

  /// Allocates space through the boxed allocator
  static box<IsCopyable, T, Allocator>*
  box_allocate(box<IsCopyable, T, Allocator> const* me) {
    real_allocator allocator_(*static_cast<Allocator const*>(me));

    return static_cast<box<IsCopyable, T, Allocator>*>(
        std::allocator_traits<real_allocator>::allocate(allocator_, 1U));
  }

  /// Destroys the box through the given allocator
  static void box_deallocate(box<IsCopyable, T, Allocator>* me) {
    real_allocator allocator_(*static_cast<Allocator const*>(me));

    me->~box();
    std::allocator_traits<real_allocator>::deallocate(allocator_, me, 1U);
  }
};

/// Creates a box containing the given value and allocator
template <bool IsCopyable, typename T, typename Allocator>
auto make_box(std::integral_constant<bool, IsCopyable>, T&& value,
              Allocator&& allocator_) {
  return box<IsCopyable, std::decay_t<T>, std::decay_t<Allocator>>(
      std::forward<T>(value), std::forward<Allocator>(allocator_));
}

template <typename T>
struct is_box : std::false_type {};
template <bool IsCopyable, typename T, typename Allocator>
struct is_box<box<IsCopyable, T, Allocator>> : 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) {
  }
  explicit constexpr data_accessor(void const* ptr) noexcept
      : data_accessor(const_cast<void*>(ptr)) {
  }

  constexpr void assign_ptr(void* ptr) noexcept {
    ptr_ = ptr;
  }
  constexpr void assign_ptr(void const* ptr) noexcept {
    ptr_ = const_cast<void*>(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 <typename From, typename To>
using transfer_const_t =
    std::conditional_t<std::is_const<std::remove_pointer_t<From>>::value,
                       std::add_const_t<To>, To>;
template <typename From, typename To>
using transfer_volatile_t =
    std::conditional_t<std::is_volatile<std::remove_pointer_t<From>>::value,
                       std::add_volatile_t<To>, To>;

/// The retriever when the object is allocated inplace
template <typename T, typename Accessor>
FU2_DETAIL_CXX14_CONSTEXPR auto retrieve(std::true_type /*is_inplace*/,
                                         Accessor from,
                                         std::size_t from_capacity) {
  using type = transfer_const_t<Accessor, transfer_volatile_t<Accessor, void>>*;

  /// Process the command by using the data inside the internal capacity
  auto storage = &(from->inplace_storage_);
  auto inplace = const_cast<void*>(static_cast<type>(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 <typename T, typename Accessor>
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 <bool IsNoexcept>
[[noreturn]] void throw_or_abortnoexcept(
    std::integral_constant<bool, IsNoexcept> /*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 <typename T>
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 <typename Ret, typename... Args>                                    \
  struct function_trait<Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT> {        \
    using pointer_type = Ret (*)(data_accessor CONST VOLATILE*,                \
                                 std::size_t capacity, Args...);               \
    template <typename T, bool IsInplace>                                      \
    struct internal_invoker {                                                  \
      static Ret invoke(data_accessor CONST VOLATILE* data,                    \
                        std::size_t capacity, Args... args) NOEXCEPT {         \
        auto obj = retrieve<T>(std::integral_constant<bool, IsInplace>{},      \
                               data, capacity);                                \
        auto box = static_cast<T CONST VOLATILE*>(obj);                        \
        return invocation::invoke(                                             \
            static_cast<std::decay_t<decltype(box->value_)> CONST VOLATILE     \
                            REF>(box->value_),                                 \
            std::forward<Args>(args)...);                                      \
      }                                                                        \
    };                                                                         \
                                                                               \
    template <typename T>                                                      \
    struct view_invoker {                                                      \
      static Ret invoke(data_accessor CONST VOLATILE* data, std::size_t,       \
                        Args... args) NOEXCEPT {                               \
                                                                               \
        auto ptr = static_cast<void CONST VOLATILE*>(data->ptr_);              \
        return invocation::invoke(address_taker<T>::restore(ptr),              \
                                  std::forward<Args>(args)...);                \
      }                                                                        \
    };                                                                         \
                                                                               \
    template <typename T>                                                      \
    using callable = T CONST VOLATILE REF;                                     \
                                                                               \
    using arguments = identity<Args...>;                                       \
                                                                               \
    using is_noexcept = is_noexcept_##NOEXCEPT;                                \
                                                                               \
    template <bool Throws>                                                     \
    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<bool, Throws>{});      \
      }                                                                        \
    };                                                                         \
  };

FU2_DETAIL_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT)
#undef FU2_DEFINE_FUNCTION_TRAIT

/// Deduces to the function pointer to the given signature
template <typename Signature>
using function_pointer_of = typename function_trait<Signature>::pointer_type;

template <typename... Args>
struct invoke_table;

/// We optimize the vtable_t in case there is a single function overload
template <typename First>
struct invoke_table<First> {
  using type = function_pointer_of<First>;

  /// Return the function pointer itself
  template <std::size_t Index>
  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 <typename T, bool IsInplace>
  static constexpr type get_invocation_table_of() noexcept {
    return &function_trait<First>::template internal_invoker<T,
                                                             IsInplace>::invoke;
  }
  /// Returns the thunk of an single overloaded callable
  template <typename T>
  static constexpr type get_invocation_view_table_of() noexcept {
    return &function_trait<First>::template view_invoker<T>::invoke;
  }
  /// Returns the thunk of an empty single overloaded callable
  template <bool IsThrowing>
  static constexpr type get_empty_invocation_table() noexcept {
    return &function_trait<First>::template empty_invoker<IsThrowing>::invoke;
  }
};
/// We generate a table in case of multiple function overloads
template <typename First, typename Second, typename... Args>
struct invoke_table<First, Second, Args...> {
  using type =
      std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
                 function_pointer_of<Args>...> const*;

  /// Return the function pointer at the particular index
  template <std::size_t Index>
  static constexpr auto fetch(type table) noexcept {
    return std::get<Index>(*table);
  }

  /// The invocation vtable for a present object
  template <typename T, bool IsInplace>
  struct invocation_vtable : public std::tuple<function_pointer_of<First>,
                                               function_pointer_of<Second>,
                                               function_pointer_of<Args>...> {
    constexpr invocation_vtable() noexcept
        : std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
                     function_pointer_of<Args>...>(std::make_tuple(
              &function_trait<First>::template internal_invoker<
                  T, IsInplace>::invoke,
              &function_trait<Second>::template internal_invoker<
                  T, IsInplace>::invoke,
              &function_trait<Args>::template internal_invoker<
                  T, IsInplace>::invoke...)) {
    }
  };

  /// Returns the thunk of an multi overloaded callable
  template <typename T, bool IsInplace>
  static type get_invocation_table_of() noexcept {
    static invocation_vtable<T, IsInplace> const table;
    return &table;
  }

  /// The invocation vtable for a present object
  template <typename T>
  struct invocation_view_vtable
      : public std::tuple<function_pointer_of<First>,
                          function_pointer_of<Second>,
                          function_pointer_of<Args>...> {
    constexpr invocation_view_vtable() noexcept
        : std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
                     function_pointer_of<Args>...>(std::make_tuple(
              &function_trait<First>::template view_invoker<T>::invoke,
              &function_trait<Second>::template view_invoker<T>::invoke,
              &function_trait<Args>::template view_invoker<T>::invoke...)) {
    }
  };

  /// Returns the thunk of an multi overloaded callable
  template <typename T>
  static type get_invocation_view_table_of() noexcept {
    static invocation_view_vtable<T> const table;
    return &table;
  }

  /// The invocation table for an empty wrapper
  template <bool IsThrowing>
  struct empty_vtable : public std::tuple<function_pointer_of<First>,
                                          function_pointer_of<Second>,
                                          function_pointer_of<Args>...> {
    constexpr empty_vtable() noexcept
        : std::tuple<function_pointer_of<First>, function_pointer_of<Second>,
                     function_pointer_of<Args>...>(
              std::make_tuple(&function_trait<First>::template empty_invoker<
                                  IsThrowing>::invoke,
                              &function_trait<Second>::template empty_invoker<
                                  IsThrowing>::invoke,
                              &function_trait<Args>::template empty_invoker<
                                  IsThrowing>::invoke...)) {
    }
  };

  /// Returns the thunk of an multi single overloaded callable
  template <bool IsThrowing>
  static type get_empty_invocation_table() noexcept {
    static empty_vtable<IsThrowing> const table;
    return &table;
  }
};

template <std::size_t Index, typename Function, typename... Signatures>
class operator_impl;

#define FU2_DEFINE_FUNCTION_TRAIT(CONST, VOLATILE, NOEXCEPT, OVL_REF, REF)     \
  template <std::size_t Index, typename Function, typename Ret,                \
            typename... Args, typename Next, typename... Signatures>           \
  class operator_impl<Index, Function,                                         \
                      Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT, Next,      \
                      Signatures...>                                           \
      : operator_impl<Index + 1, Function, Next, Signatures...> {              \
                                                                               \
    template <std::size_t, typename, typename...>                              \
    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<Index + 1, Function, Next, Signatures...>::operator(); \
                                                                               \
    Ret operator()(Args... args) CONST VOLATILE OVL_REF NOEXCEPT {             \
      auto parent = static_cast<Function CONST VOLATILE*>(this);               \
      using erasure_t = std::decay_t<decltype(parent->erasure_)>;              \
                                                                               \
      /* `std::decay_t<decltype(parent->erasure_)>` is a workaround for a   */ \
      /* compiler regression of MSVC 16.3.1, see #29 for details.           */ \
      return std::decay_t<decltype(parent->erasure_)>::template invoke<Index>( \
          static_cast<erasure_t CONST VOLATILE REF>(parent->erasure_),         \
          std::forward<Args>(args)...);                                        \
    }                                                                          \
  };                                                                           \
  template <std::size_t Index, typename Config, typename Property,             \
            typename Ret, typename... Args>                                    \
  class operator_impl<Index, function<Config, Property>,                       \
                      Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT>            \
      : copyable<!Config::is_owning || Config::is_copyable> {                  \
                                                                               \
    template <std::size_t, typename, typename...>                              \
    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<function<Config, Property> CONST VOLATILE*>(this);       \
      using erasure_t = std::decay_t<decltype(parent->erasure_)>;              \
                                                                               \
      /* `std::decay_t<decltype(parent->erasure_)>` is a workaround for a   */ \
      /* compiler regression of MSVC 16.3.1, see #29 for details.           */ \
      return std::decay_t<decltype(parent->erasure_)>::template invoke<Index>( \
          static_cast<erasure_t CONST VOLATILE REF>(parent->erasure_),         \
          std::forward<Args>(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 <typename Property>
class vtable;
template <bool IsThrowing, bool HasStrongExceptGuarantee,
          typename... FormalArgs>
class vtable<property<IsThrowing, HasStrongExceptGuarantee, FormalArgs...>> {
  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<FormalArgs...>;

  command_function_t cmd_;
  typename invoke_table_t::type vtable_;

  template <typename T>
  struct trait {
    static_assert(is_box<T>::value,
                  "The trait must be specialized with a box!");

    /// The command table
    template <bool IsInplace>
    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<T*>(retrieve<T>(
              std::integral_constant<bool, IsInplace>{}, 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<T>();

          }
          // 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<T const*>(retrieve<T>(
              std::integral_constant<bool, IsInplace>{}, from, from_capacity));
          assert(box && "The object must not be over aligned or null!");

          assert(std::is_copy_constructible<T>::value &&
                 "The box is required to be copyable here!");

          // Try to allocate the object inplace
          construct(std::is_copy_constructible<T>{}, *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<T*>(retrieve<T>(
              std::integral_constant<bool, IsInplace>{}, from, from_capacity));

          if (IsInplace) {
            box->~T();
          } else {
            box_factory<T>::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 <typename Box>
    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<T>(std::true_type{}, to, to_capacity);
      if (storage) {
        to_table->template set_inplace<T>();
      } else {
        // Allocate the object through the allocator
        to->ptr_ = storage =
            box_factory<std::decay_t<Box>>::box_allocate(std::addressof(box));
        to_table->template set_allocated<T>();
      }
      new (storage) T(std::forward<Box>(box));
    }

    template <typename Box>
    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 <typename T>
  static void init(vtable& table, T&& object, data_accessor* to,
                   std::size_t to_capacity) {

    trait<std::decay_t<T>>::construct(std::true_type{}, std::forward<T>(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<data_accessor*>(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 <std::size_t Index, typename... Args>
  constexpr decltype(auto) invoke(Args&&... args) const {
    auto thunk = invoke_table_t::template fetch<Index>(vtable_);
    return thunk(std::forward<Args>(args)...);
  }
  /// Invoke the function at the given index
  template <std::size_t Index, typename... Args>
  constexpr decltype(auto) invoke(Args&&... args) const volatile {
    auto thunk = invoke_table_t::template fetch<Index>(vtable_);
    return thunk(std::forward<Args>(args)...);
  }

  template <typename T>
  void set_inplace() noexcept {
    using type = std::decay_t<T>;
    vtable_ = invoke_table_t::template get_invocation_table_of<type, true>();
    cmd_ = &trait<type>::template process_cmd<true>;
  }

  template <typename T>
  void set_allocated() noexcept {
    using type = std::decay_t<T>;
    vtable_ = invoke_table_t::template get_invocation_table_of<type, false>();
    cmd_ = &trait<type>::template process_cmd<false>;
  }

  void set_empty() noexcept {
    vtable_ = invoke_table_t::template get_empty_invocation_table<IsThrowing>();
    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 <typename Capacity, typename = void>
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::capacity, Capacity::alignment> capacity_;
  } type;
};
template <typename Capacity>
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 <typename Capacity>
class internal_capacity_holder {
  // Tag to access the structure in a type-safe way
  typename internal_capacity<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 <bool IsOwning /* = true*/, typename Config, typename Property>
class erasure : internal_capacity_holder<typename Config::capacity> {
  template <bool, typename, typename>
  friend class erasure;
  template <std::size_t, typename, typename...>
  friend class operator_impl;

  using vtable_t = tables::vtable<Property>;

  vtable_t vtable_;

public:
  /// Returns the capacity of this erasure
  static constexpr std::size_t capacity() noexcept {
    return internal_capacity_holder<typename Config::capacity>::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 <typename OtherConfig>
  FU2_DETAIL_CXX14_CONSTEXPR
  erasure(erasure<true, OtherConfig, Property> right) noexcept(
      Property::is_strong_exception_guaranteed) {
    right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(),
                       this->opaque_ptr(), capacity());
  }

  template <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
  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<bool, Config::is_copyable>{},
                       std::forward<T>(callable),
                       std::forward<Allocator>(allocator_)),
                   this->opaque_ptr(), capacity());
  }
  template <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
  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<bool, Config::is_copyable>{},
                         std::forward<T>(callable),
                         std::forward<Allocator>(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 <typename OtherConfig>
  FU2_DETAIL_CXX14_CONSTEXPR erasure&
  operator=(erasure<true, OtherConfig, Property> 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 <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
  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<bool, Config::is_copyable>{},
                       std::forward<T>(callable),
                       std::forward<Allocator>(allocator_)),
                   this->opaque_ptr(), capacity());
  }

  template <typename T, typename Allocator = std::allocator<std::decay_t<T>>>
  void assign(std::true_type /*use_bool_op*/, T&& callable,
              Allocator&& allocator_ = {}) {
    if (bool(callable)) {
      assign(std::false_type{}, std::forward<T>(callable),
             std::forward<Allocator>(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 <std::size_t Index, typename Erasure, typename... Args>
  static constexpr decltype(auto) invoke(Erasure&& erasure, Args&&... args) {
    auto const capacity = erasure.capacity();
    return erasure.vtable_.template invoke<Index>(
        std::forward<Erasure>(erasure).opaque_ptr(), capacity,
        std::forward<Args>(args)...);
  }
};

// A non owning erasure
template </*bool IsOwning = false, */ typename Config, bool IsThrowing,
          bool HasStrongExceptGuarantee, typename... Args>
class erasure<false, Config,
              property<IsThrowing, HasStrongExceptGuarantee, Args...>> {
  template <bool, typename, typename>
  friend class erasure;
  template <std::size_t, typename, typename...>
  friend class operator_impl;

  using property_t = property<IsThrowing, HasStrongExceptGuarantee, Args...>;

  using invoke_table_t = invocation_table::invoke_table<Args...>;
  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<IsThrowing>()),
        view_(nullptr) {
  }

  // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
  constexpr erasure(std::nullptr_t) noexcept
      : invoke_table_(
            invoke_table_t::template get_empty_invocation_table<IsThrowing>()),
        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 <typename OtherConfig>
  // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
  constexpr erasure(erasure<false, OtherConfig, property_t> right) noexcept
      : invoke_table_(right.invoke_table_), view_(right.view_) {
  }

  template <typename T>
  // 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<T>>()),
        view_(address_taker<std::decay_t<T>>::take(std::forward<T>(object))) {
  }
  template <typename T>
  // 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<T>(object));
  }

  ~erasure() = default;

  constexpr erasure&
  operator=(std::nullptr_t) noexcept(HasStrongExceptGuarantee) {
    invoke_table_ =
        invoke_table_t::template get_empty_invocation_table<IsThrowing>();
    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 <typename OtherConfig>
  constexpr erasure&
  operator=(erasure<true, OtherConfig, property_t> right) noexcept {
    invoke_table_ = right.invoke_table_;
    view_ = right.view_;
    return *this;
  }

  template <typename T>
  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<T>>();
    view_.assign_ptr(
        address_taker<std::decay_t<T>>::take(std::forward<T>(callable)));
  }
  template <typename T>
  constexpr void assign(std::true_type /*use_bool_op*/, T&& callable) {
    if (bool(callable)) {
      assign(std::false_type{}, std::forward<T>(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 <std::size_t Index, typename Erasure, typename... T>
  static constexpr decltype(auto) invoke(Erasure&& erasure, T&&... args) {
    auto thunk = invoke_table_t::template fetch<Index>(erasure.invoke_table_);
    return thunk(&(erasure.view_), 0UL, std::forward<T>(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 <typename T, typename Signature,
          typename Trait =
              type_erasure::invocation_table::function_trait<Signature>>
struct accepts_one
    : detail::lazy_and< // both are std::integral_constant
          invocation::can_invoke<typename Trait::template callable<T>,
                                 typename Trait::arguments>,
          invocation::is_noexcept_correct<Trait::is_noexcept::value,
                                          typename Trait::template callable<T>,
                                          typename Trait::arguments>> {};

/// Deduces to a true_type if the type T provides all signatures
template <typename T, typename Signatures, typename = void>
struct accepts_all : std::false_type {};
template <typename T, typename... Signatures>
struct accepts_all<
    T, identity<Signatures...>,
    void_t<std::enable_if_t<accepts_one<T, Signatures>::value>...>>
    : std::true_type {};

#if defined(FU2_HAS_NO_EMPTY_PROPAGATION)
template <typename T>
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 <typename T>
struct use_bool_op : std::false_type {};

#if !defined(FU2_HAS_NO_FUNCTIONAL_HEADER)
template <typename Signature>
struct use_bool_op<std::function<Signature>> : std::true_type {};
#endif

template <typename Config, typename Property>
struct use_bool_op<function<Config, Property>> : std::true_type {};

template <typename T>
struct use_bool_op<T*> : std::true_type {};

template <typename Class, typename T>
struct use_bool_op<T Class::*> : std::true_type {};
#else
template <typename T, typename = void>
struct has_bool_op : std::false_type {};
template <typename T>
struct has_bool_op<T, void_t<decltype(bool(std::declval<T>()))>>
    : std::true_type {
#ifndef NDEBUG
  static_assert(!std::is_pointer<T>::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 <typename T>
struct use_bool_op : has_bool_op<T> {};

#define FU2_DEFINE_USE_OP_TRAIT(CONST, VOLATILE, NOEXCEPT)                     \
  template <typename Ret, typename... Args>                                    \
  struct use_bool_op<Ret (*CONST VOLATILE)(Args...) NOEXCEPT>                  \
      : std::true_type {};

FU2_DETAIL_EXPAND_CV(FU2_DEFINE_USE_OP_TRAIT)
#undef FU2_DEFINE_USE_OP_TRAIT

template <typename Ret, typename... Args>
struct use_bool_op<Ret(Args...)> : std::false_type {};

#if defined(FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE)
template <typename Ret, typename... Args>
struct use_bool_op<Ret(Args...) noexcept> : std::false_type {};
#endif
#endif // FU2_HAS_NO_EMPTY_PROPAGATION

template <typename Config, typename T>
struct assert_wrong_copy_assign {
  static_assert(!Config::is_owning || !Config::is_copyable ||
                    std::is_copy_constructible<std::decay_t<T>>::value,
                "Can't wrap a non copyable object into a unique function!");

  using type = void;
};

template <bool IsStrongExceptGuaranteed, typename T>
struct assert_no_strong_except_guarantee {
  static_assert(
      !IsStrongExceptGuaranteed ||
          (std::is_nothrow_move_constructible<T>::value &&
           std::is_nothrow_destructible<T>::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 <typename LeftConfig, typename RightConfig>
using enable_if_copyable_correct_t =
    std::enable_if_t<(!LeftConfig::is_copyable || RightConfig::is_copyable)>;

template <typename LeftConfig, typename RightConfig>
using is_owning_correct =
    std::integral_constant<bool,
                           (LeftConfig::is_owning == RightConfig::is_owning)>;

/// SFINAES out if the given function2 is not owning correct to this one
template <typename LeftConfig, typename RightConfig>
using enable_if_owning_correct_t =
    std::enable_if_t<is_owning_correct<LeftConfig, RightConfig>::value>;

template <typename Config, bool IsThrowing, bool HasStrongExceptGuarantee,
          typename... Args>
class function<Config, property<IsThrowing, HasStrongExceptGuarantee, Args...>>
    : type_erasure::invocation_table::operator_impl<
          0U,
          function<Config,
                   property<IsThrowing, HasStrongExceptGuarantee, Args...>>,
          Args...> {

  template <typename, typename>
  friend class function;

  template <std::size_t, typename, typename...>
  friend class type_erasure::invocation_table::operator_impl;

  using property_t = property<IsThrowing, HasStrongExceptGuarantee, Args...>;
  using erasure_t =
      type_erasure::erasure<Config::is_owning, Config, property_t>;

  template <typename T>
  using enable_if_can_accept_all_t =
      std::enable_if_t<accepts_all<std::decay_t<T>, identity<Args...>>::value>;

  template <typename Function, typename = void>
  struct is_convertible_to_this : std::false_type {};
  template <typename RightConfig>
  struct is_convertible_to_this<
      function<RightConfig, property_t>,
      void_t<enable_if_copyable_correct_t<Config, RightConfig>,
             enable_if_owning_correct_t<Config, RightConfig>>>
      : std::true_type {};

  template <typename T>
  using enable_if_not_convertible_to_this =
      std::enable_if_t<!is_convertible_to_this<std::decay_t<T>>::value>;

  template <typename T>
  using enable_if_owning_t =
      std::enable_if_t<std::is_same<T, T>::value && Config::is_owning>;

  template <typename T>
  using assert_wrong_copy_assign_t =
      typename assert_wrong_copy_assign<Config, std::decay_t<T>>::type;

  template <typename T>
  using assert_no_strong_except_guarantee_t =
      typename assert_no_strong_except_guarantee<HasStrongExceptGuarantee,
                                                 std::decay_t<T>>::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 <typename RightConfig,
            std::enable_if_t<RightConfig::is_copyable>* = nullptr,
            enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
            enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
  FU2_DETAIL_CXX14_CONSTEXPR
  function(function<RightConfig, property_t> const& right)
      : erasure_(right.erasure_) {
  }

  /// Move construction from another function
  template <typename RightConfig,
            enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
            enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
  FU2_DETAIL_CXX14_CONSTEXPR function(function<RightConfig, property_t>&& right)
      : erasure_(std::move(right.erasure_)) {
  }

  /// Construction from a callable object which overloads the `()` operator
  template <typename T, //
            enable_if_not_convertible_to_this<T>* = nullptr,
            enable_if_can_accept_all_t<T>* = nullptr,
            assert_wrong_copy_assign_t<T>* = nullptr,
            assert_no_strong_except_guarantee_t<T>* = nullptr>
  FU2_DETAIL_CXX14_CONSTEXPR function(T&& callable)
      : erasure_(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable)) {
  }
  template <typename T, typename Allocator, //
            enable_if_not_convertible_to_this<T>* = nullptr,
            enable_if_can_accept_all_t<T>* = nullptr,
            enable_if_owning_t<T>* = nullptr,
            assert_wrong_copy_assign_t<T>* = nullptr,
            assert_no_strong_except_guarantee_t<T>* = nullptr>
  FU2_DETAIL_CXX14_CONSTEXPR function(T&& callable, Allocator&& allocator_)
      : erasure_(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable),
                 std::forward<Allocator>(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 <typename RightConfig,
            std::enable_if_t<RightConfig::is_copyable>* = nullptr,
            enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
            enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
  function& operator=(function<RightConfig, property_t> const& right) {
    erasure_ = right.erasure_;
    return *this;
  }

  /// Move assigning from another function
  template <typename RightConfig,
            enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
            enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
  function& operator=(function<RightConfig, property_t>&& right) {
    erasure_ = std::move(right.erasure_);
    return *this;
  }

  /// Move assigning from a callable object
  template <typename T, // ...
            enable_if_not_convertible_to_this<T>* = nullptr,
            enable_if_can_accept_all_t<T>* = nullptr,
            assert_wrong_copy_assign_t<T>* = nullptr,
            assert_no_strong_except_guarantee_t<T>* = nullptr>
  function& operator=(T&& callable) {
    erasure_.assign(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(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 <typename T, typename Allocator = std::allocator<std::decay_t<T>>,
            enable_if_not_convertible_to_this<T>* = nullptr,
            enable_if_can_accept_all_t<T>* = nullptr,
            assert_wrong_copy_assign_t<T>* = nullptr,
            assert_no_strong_except_guarantee_t<T>* = nullptr>
  void assign(T&& callable, Allocator&& allocator_ = Allocator{}) {
    erasure_.assign(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable),
                    std::forward<Allocator>(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<Config, property_t>, Args...>::operator();
};

template <typename Config, typename Property>
bool operator==(function<Config, Property> const& f, std::nullptr_t) {
  return !bool(f);
}

template <typename Config, typename Property>
bool operator!=(function<Config, Property> const& f, std::nullptr_t) {
  return bool(f);
}

template <typename Config, typename Property>
bool operator==(std::nullptr_t, function<Config, Property> const& f) {
  return !bool(f);
}

template <typename Config, typename Property>
bool operator!=(std::nullptr_t, function<Config, Property> const& f) {
  return bool(f);
}

// Default intended object size of the function
using object_size = std::integral_constant<std::size_t, 32U>;
} // 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 <std::size_t Capacity,
          std::size_t Alignment = alignof(std::max_align_t)>
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<detail::object_size::value - (2 * sizeof(void*))> {};

/// 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 <typename T>
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 <bool IsOwning, bool IsCopyable, typename Capacity, bool IsThrowing,
          bool HasStrongExceptGuarantee, typename... Signatures>
using function_base = detail::function<
    detail::config<IsOwning, IsCopyable, Capacity>,
    detail::property<IsThrowing, HasStrongExceptGuarantee, Signatures...>>;

/// An owning copyable function wrapper for arbitrary callable types.
template <typename... Signatures>
using function = function_base<true, true, capacity_default, //
                               true, false, Signatures...>;

/// An owning non copyable function wrapper for arbitrary callable types.
template <typename... Signatures>
using unique_function = function_base<true, false, capacity_default, //
                                      true, false, Signatures...>;

/// A non owning copyable function wrapper for arbitrary callable types.
template <typename... Signatures>
using function_view = function_base<false, true, capacity_default, //
                                    true, false, Signatures...>;

#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 <typename... T>
constexpr auto overload(T&&... callables) {
  return detail::overloading::overload(std::forward<T>(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_
