-// Copyright 2015-2018 Denis Blank <denis.blank at outlook dot com>
+// 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)
#define FU2_INCLUDED_FUNCTION2_HPP_
#include <cassert>
+#include <cstddef>
#include <cstdlib>
#include <memory>
#include <tuple>
#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)
-#define 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)
#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_310 {
+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 is targets a bug in GCC,
+// Equivalent to C++17's std::void_t which targets a bug in GCC,
// that prevents correct SFINAE behavior.
// See http://stackoverflow.com/questions/35753920 for details.
template <typename...>
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 {};
};
/// Configuration trait to configure the function_base class.
-template <bool Owning, bool Copyable, std::size_t Capacity>
+template <bool Owning, bool Copyable, typename Capacity>
struct config {
- // Is true if the function is copyable.
+ // Is true if the function is owning.
static constexpr auto const is_owning = Owning;
// Is true if the function is copyable.
// The internal capacity of the function
// used in small functor optimization.
- static constexpr auto const capacity = Capacity;
+ // 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
static constexpr auto const is_throwing = Throws;
// Is true when the function throws an exception on empty invocation.
- static constexpr auto const is_strong_exception_guaranteed = Throws;
+ static constexpr auto const is_strong_exception_guaranteed =
+ HasStrongExceptGuarantee;
};
+#ifndef NDEBUG
+[[noreturn]] inline void unreachable_debug() {
+ FU2_DETAIL_TRAP();
+ std::abort();
+}
+#endif
+
/// Provides utilities for invocing callable objects
namespace invocation {
/// Invokes the given callable object with the given arguments
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)...)) {
+ -> 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
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 {};
+ 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 {};
+ 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>()))>
};
template <typename Pointer, typename T>
struct can_invoke<Pointer, identity<T*>,
- decltype(
- (void)(std::declval<T*>()->*std::declval<Pointer>()))>
+ 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(invoke(std::declval<T>(),
- std::declval<Args>()...))> {
+ : std::integral_constant<bool,
+ noexcept(::fu2::detail::invocation::invoke(
+ std::declval<T>(), std::declval<Args>()...))> {
};
} // end namespace invocation
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)...) {
+ : Current(std::move(current)),
+ overload_impl<Next, Rest...>(std::move(next), std::move(rest)...) {
}
using Current::operator();
template <typename T, typename = void>
struct address_taker {
template <typename O>
- static void* take(O&& obj) {
+ static auto take(O&& obj) {
return std::addressof(obj);
}
static T& restore(void* ptr) {
T value_;
- explicit box(T value, Allocator allocator)
- : Allocator(std::move(allocator)), value_(std::move(value)) {
+ explicit box(T value, Allocator allocator_)
+ : Allocator(std::move(allocator_)), value_(std::move(value)) {
}
box(box&&) = default;
T value_;
- explicit box(T value, Allocator allocator)
- : Allocator(std::move(allocator)), value_(std::move(value)) {
+ explicit box(T value, Allocator allocator_)
+ : Allocator(std::move(allocator_)), value_(std::move(value)) {
}
box(box&&) = default;
/// 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));
+ real_allocator allocator_(*static_cast<Allocator const*>(me));
return static_cast<box<IsCopyable, T, Allocator>*>(
- std::allocator_traits<real_allocator>::allocate(allocator, 1U));
+ 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));
+ real_allocator allocator_(*static_cast<Allocator const*>(me));
me->~box();
- std::allocator_traits<real_allocator>::deallocate(allocator, me, 1U);
+ 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 = std::allocator<std::decay_t<T>>>
+template <bool IsCopyable, typename T, typename Allocator>
auto make_box(std::integral_constant<bool, IsCopyable>, T&& value,
- Allocator&& allocator = Allocator{}) {
- return box<IsCopyable, std::decay_t<T>, std::decay_t<Allocator>>{
- std::forward<T>(value), std::forward<Allocator>(allocator)};
+ Allocator&& allocator_) {
+ return box<IsCopyable, std::decay_t<T>, std::decay_t<Allocator>>(
+ std::forward<T>(value), std::forward<Allocator>(allocator_));
}
template <typename T>
}
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_;
};
/// See opcode::op_fetch_empty
-constexpr void write_empty(data_accessor* accessor, bool empty) noexcept {
+static FU2_DETAIL_CXX14_CONSTEXPR void write_empty(data_accessor* accessor,
+ bool empty) noexcept {
accessor->inplace_storage_ = std::size_t(empty);
}
/// The retriever when the object is allocated inplace
template <typename T, typename Accessor>
-constexpr auto retrieve(std::true_type /*is_inplace*/, Accessor from,
- std::size_t from_capacity) {
+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
return "bad function call";
}
};
-#elif
+#else
using std::bad_function_call;
#endif
#endif
#ifdef FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
-#define FU2_EXPAND_QUALIFIERS_NOEXCEPT(F) \
+#define FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F) \
F(, , noexcept, , &) \
F(const, , noexcept, , &) \
F(, volatile, noexcept, , &) \
F(const, , noexcept, &&, &&) \
F(, volatile, noexcept, &&, &&) \
F(const, volatile, noexcept, &&, &&)
+#define FU2_DETAIL_EXPAND_CV_NOEXCEPT(F) \
+ F(, , noexcept) \
+ F(const, , noexcept) \
+ F(, volatile, noexcept) \
+ F(const, volatile, noexcept)
#else // FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
-#define FU2_EXPAND_QUALIFIERS_NOEXCEPT(F)
+#define FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F)
+#define FU2_DETAIL_EXPAND_CV_NOEXCEPT(F)
#endif // FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE
-#define FU2_EXPAND_QUALIFIERS(F) \
+#define FU2_DETAIL_EXPAND_QUALIFIERS(F) \
F(, , , , &) \
F(const, , , , &) \
F(, volatile, , , &) \
F(const, , , &&, &&) \
F(, volatile, , &&, &&) \
F(const, volatile, , &&, &&) \
- FU2_EXPAND_QUALIFIERS_NOEXCEPT(F)
+ FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F)
+#define FU2_DETAIL_EXPAND_CV(F) \
+ F(, , ) \
+ F(const, , ) \
+ F(, volatile, ) \
+ F(const, volatile, ) \
+ FU2_DETAIL_EXPAND_CV_NOEXCEPT(F)
/// If the function is qualified as noexcept, the call will never throw
template <bool IsNoexcept>
}; \
};
-FU2_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT)
+FU2_DETAIL_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT)
#undef FU2_DEFINE_FUNCTION_TRAIT
/// Deduces to the function pointer to the given signature
auto parent = static_cast<Function CONST VOLATILE*>(this); \
using erasure_t = std::decay_t<decltype(parent->erasure_)>; \
\
- return erasure_t::template invoke<Index>( \
+ /* `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)...); \
} \
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> { \
+ : copyable<!Config::is_owning || Config::is_copyable> { \
\
template <std::size_t, typename, typename...> \
friend class operator_impl; \
static_cast<function<Config, Property> CONST VOLATILE*>(this); \
using erasure_t = std::decay_t<decltype(parent->erasure_)>; \
\
- return erasure_t::template invoke<Index>( \
+ /* `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_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT)
+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
+ 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 Use an unreachable intrinsic
- assert(false && "Unreachable!");
- std::exit(-1);
+ FU2_DETAIL_UNREACHABLE();
}
template <typename Box>
write_empty(to, true);
break;
}
+ default: {
+ FU2_DETAIL_UNREACHABLE();
+ }
}
}
/// Invoke the function at the given index
template <std::size_t Index, typename... Args>
- constexpr auto invoke(Args&&... args) const {
+ 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 auto invoke(Args&&... args) const volatile {
+ constexpr decltype(auto) invoke(Args&&... args) const volatile {
auto thunk = invoke_table_t::template fetch<Index>(vtable_);
return thunk(std::forward<Args>(args)...);
}
/// same space with the internal capacity.
/// The storage type is distinguished by multiple versions of the
/// control and vtable.
-template <std::size_t Capacity, typename = void>
+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_;
+ struct alignas(Capacity::alignment) {
+ unsigned char data[Capacity::capacity];
+ } capacity_;
} type;
};
-template <std::size_t Capacity>
-struct internal_capacity<Capacity,
- std::enable_if_t<(Capacity < sizeof(void*))>> {
+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 <std::size_t Capacity>
+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;
- constexpr data_accessor* opaque_ptr() noexcept {
+ FU2_DETAIL_CXX14_CONSTEXPR data_accessor* opaque_ptr() noexcept {
return &storage_.accessor_;
}
constexpr data_accessor const* opaque_ptr() const noexcept {
return &storage_.accessor_;
}
- constexpr data_accessor volatile* opaque_ptr() volatile noexcept {
+ FU2_DETAIL_CXX14_CONSTEXPR data_accessor volatile*
+ opaque_ptr() volatile noexcept {
return &storage_.accessor_;
}
constexpr data_accessor const volatile* opaque_ptr() const volatile noexcept {
/// An owning erasure
template <bool IsOwning /* = true*/, typename Config, typename Property>
-class erasure : internal_capacity_holder<Config::capacity> {
+class erasure : internal_capacity_holder<typename Config::capacity> {
template <bool, typename, typename>
friend class erasure;
template <std::size_t, typename, typename...>
public:
/// Returns the capacity of this erasure
static constexpr std::size_t capacity() noexcept {
- return internal_capacity_holder<Config::capacity>::capacity();
+ return internal_capacity_holder<typename Config::capacity>::capacity();
}
- constexpr erasure() noexcept {
+ FU2_DETAIL_CXX14_CONSTEXPR erasure() noexcept {
vtable_.set_empty();
}
- constexpr erasure(std::nullptr_t) noexcept {
+ FU2_DETAIL_CXX14_CONSTEXPR erasure(std::nullptr_t) noexcept {
vtable_.set_empty();
}
- constexpr erasure(erasure&& right) noexcept(
- Property::is_strong_exception_guaranteed) {
+ 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());
}
- constexpr erasure(erasure const& right) {
+ FU2_DETAIL_CXX14_CONSTEXPR erasure(erasure const& right) {
right.vtable_.copy(vtable_, right.opaque_ptr(), right.capacity(),
this->opaque_ptr(), capacity());
}
template <typename OtherConfig>
- constexpr erasure(erasure<true, OtherConfig, Property> right) noexcept(
+ 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>>>
- constexpr erasure(T&& callable, Allocator&& allocator = Allocator{}) {
+ 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)),
+ 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());
}
- constexpr erasure&
+ FU2_DETAIL_CXX14_CONSTEXPR erasure&
operator=(std::nullptr_t) noexcept(Property::is_strong_exception_guaranteed) {
vtable_.destroy(this->opaque_ptr(), capacity());
return *this;
}
- constexpr erasure& operator=(erasure&& right) noexcept(
+ 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(),
return *this;
}
- constexpr erasure& operator=(erasure const& right) {
+ 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());
}
template <typename OtherConfig>
- constexpr erasure&
+ FU2_DETAIL_CXX14_CONSTEXPR erasure&
operator=(erasure<true, OtherConfig, Property> right) noexcept(
Property::is_strong_exception_guaranteed) {
vtable_.weak_destroy(this->opaque_ptr(), capacity());
return *this;
}
- template <typename T>
- constexpr erasure& operator=(T&& callable) {
+ 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<T>(callable),
+ std::forward<Allocator>(allocator_)),
this->opaque_ptr(), capacity());
- return *this;
}
- template <typename T, typename Allocator>
- void assign(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
/// 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 auto invoke(Erasure&& erasure, Args&&... 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,
template <typename T>
// NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init)
- constexpr erasure(T&& object)
+ constexpr erasure(std::false_type /*use_bool_op*/, T&& object)
: invoke_table_(invoke_table_t::template get_invocation_view_table_of<
std::decay_t<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;
}
template <typename T>
- constexpr erasure& operator=(T&& object) {
+ constexpr void assign(std::false_type /*use_bool_op*/, T&& callable) {
invoke_table_ = invoke_table_t::template get_invocation_view_table_of<
std::decay_t<T>>();
- view_.ptr_ = address_taker<std::decay_t<T>>::take(std::forward<T>(object));
- return *this;
+ 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
}
template <std::size_t Index, typename Erasure, typename... T>
- static constexpr auto invoke(Erasure&& erasure, T&&... args) {
+ 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)...);
}
typename Trait =
type_erasure::invocation_table::function_trait<Signature>>
struct accepts_one
- : std::integral_constant<
- bool, invocation::can_invoke<typename Trait::template callable<T>,
- typename Trait::arguments>::value &&
- invocation::is_noexcept_correct<
- Trait::is_noexcept::value,
- typename Trait::template callable<T>,
- typename Trait::arguments>::value> {};
+ : 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>
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_copyable ||
+ 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!");
function() = default;
~function() = default;
- explicit constexpr function(function const& /*right*/) = default;
- explicit constexpr function(function&& /*right*/) = 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>
- constexpr function(function<RightConfig, property_t> const& right)
+ FU2_DETAIL_CXX14_CONSTEXPR
+ function(function<RightConfig, property_t> const& right)
: erasure_(right.erasure_) {
}
template <typename RightConfig,
enable_if_copyable_correct_t<Config, RightConfig>* = nullptr,
enable_if_owning_correct_t<Config, RightConfig>* = nullptr>
- constexpr function(function<RightConfig, property_t>&& right)
+ FU2_DETAIL_CXX14_CONSTEXPR function(function<RightConfig, property_t>&& right)
: erasure_(std::move(right.erasure_)) {
}
enable_if_can_accept_all_t<T>* = nullptr,
assert_wrong_copy_assign_t<T>* = nullptr,
assert_no_strong_except_guarantee_t<T>* = nullptr>
- constexpr function(T&& callable) : erasure_(std::forward<T>(callable)) {
+ 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_owning_t<T>* = nullptr,
assert_wrong_copy_assign_t<T>* = nullptr,
assert_no_strong_except_guarantee_t<T>* = nullptr>
- constexpr function(T&& callable, Allocator&& allocator)
- : erasure_(std::forward<T>(callable),
- std::forward<Allocator>(allocator)) {
+ 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
- constexpr function(std::nullptr_t np) : erasure_(np) {
+ FU2_DETAIL_CXX14_CONSTEXPR function(std::nullptr_t np) : erasure_(np) {
}
function& operator=(function const& /*right*/) = default;
assert_wrong_copy_assign_t<T>* = nullptr,
assert_no_strong_except_guarantee_t<T>* = nullptr>
function& operator=(T&& callable) {
- erasure_ = std::forward<T>(callable);
+ erasure_.assign(use_bool_op<unrefcv_t<T>>{}, std::forward<T>(callable));
return *this;
}
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(std::forward<T>(callable),
- std::forward<Allocator>(allocator));
+ 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
return bool(f);
}
-// Default object size of the function
+// Default intended object size of the function
using object_size = std::integral_constant<std::size_t, 32U>;
-
-// Default capacity for small functor optimization
-using default_capacity =
- std::integral_constant<std::size_t,
- object_size::value - (2 * sizeof(void*))>;
} // namespace detail
-} // namespace abi_310
-
-/// Adaptable function wrapper base for arbitrary functional types.
-template <
- /// This is a placeholder for future non owning support
- bool IsOwning,
- /// Defines whether the function is copyable or not
- bool IsCopyable,
- /// 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*).
- std::size_t Capacity,
- /// Defines whether the function throws an exception on empty function
- /// call, `std::abort` is called otherwise.
- bool IsThrowing,
- /// Defines whether all objects satisfy the strong exception guarantees,
- /// which means the function type will satisfy the strong exception
- /// guarantees too.
- bool HasStrongExceptGuarantee,
- /// Defines the signature of the function wrapper
- typename... Signatures>
+} // 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, detail::default_capacity::value,
+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, detail::default_capacity::value, true, false,
- 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, detail::default_capacity::value, true, false,
- 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 suport is enabled if
+/// 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 functionl header
+/// 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;
}
} // namespace fu2
-#undef FU2_EXPAND_QUALIFIERS
-#undef FU2_EXPAND_QUALIFIERS_NOEXCEPT
+namespace std{
+template <typename Config, typename Property, typename Alloc>
+struct uses_allocator<
+ ::fu2::detail::function<Config, Property>,
+ Alloc
+> : std::true_type {};
+} // namespace std
+
+#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_