// 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 (!!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 (!!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 {
--> --------------------
--> maximum size reached
--> --------------------