]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
include/function2.hpp: avoid using std::aligned_storage_t 64524/head
authorKefu Chai <tchaikov@gmail.com>
Wed, 16 Jul 2025 05:59:01 +0000 (13:59 +0800)
committerKefu Chai <tchaikov@gmail.com>
Wed, 16 Jul 2025 08:52:33 +0000 (16:52 +0800)
std::aligned_storage_t was deprecated in C++23, now that we've switched
to C++23. let's address the warning like:

```
In file included from /mnt/igor/github/salieri11/ceph/src/osdc/Objecter.cc:15:
In file included from /mnt/igor/github/salieri11/ceph/src/osdc/Objecter.h:44:
/mnt/igor/github/salieri11/ceph/src/include/function2.hpp:962:10: error: 'aligned_storage_t' is deprecated [-Werror,-Wdeprecated-declarations]
  962 |     std::aligned_storage_t<Capacity> capacity_;
```

in this change, we

- update function2.hpp with upstream
- apply the fix to trade std::aligned_storage_t with an alignas-based
  equivalent implementation

Signed-off-by: Kefu Chai <tchaikov@gmail.com>
src/include/function2.hpp

index 5419e143496db11d7586002e9a22a4589c46a046..6453f706d2a6d76559f1085bed8529dcdf4fdab5 100644 (file)
@@ -1,5 +1,5 @@
 
-//  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)
@@ -8,6 +8,7 @@
 #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;
@@ -69,7 +139,7 @@ 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...>
@@ -78,6 +148,22 @@ 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 {};
@@ -92,9 +178,9 @@ struct copyable<false> {
 };
 
 /// 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.
@@ -102,7 +188,9 @@ struct config {
 
   // 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
@@ -112,9 +200,17 @@ struct property {
   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
@@ -137,8 +233,8 @@ constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept(
 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
@@ -174,14 +270,14 @@ struct can_invoke<Pointer, identity<T&, Args...>,
                       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>()))>
@@ -193,16 +289,17 @@ struct can_invoke<Pointer, identity<T&&>,
 };
 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
 
@@ -213,8 +310,8 @@ 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)...) {
+      : Current(std::move(current)),
+        overload_impl<Next, Rest...>(std::move(next), std::move(rest)...) {
   }
 
   using Current::operator();
@@ -241,7 +338,7 @@ namespace type_erasure {
 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) {
@@ -279,8 +376,8 @@ struct box : private Allocator {
 
   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;
@@ -295,8 +392,8 @@ struct box<false, T, Allocator> : private Allocator {
 
   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;
@@ -315,28 +412,27 @@ struct box_factory<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));
+    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>
@@ -352,6 +448,16 @@ union data_accessor {
   }
   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_;
@@ -360,7 +466,8 @@ union data_accessor {
 };
 
 /// 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);
 }
 
@@ -375,8 +482,9 @@ using transfer_volatile_t =
 
 /// 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
@@ -405,13 +513,13 @@ struct bad_function_call : std::exception {
     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, , &)                                                 \
@@ -424,11 +532,17 @@ using std::bad_function_call;
   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, , , &)                                                         \
@@ -441,7 +555,13 @@ using std::bad_function_call;
   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>
@@ -515,7 +635,7 @@ using is_noexcept_noexcept = std::true_type;
     };                                                                         \
   };
 
-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
@@ -666,7 +786,9 @@ class operator_impl;
       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)...);                                        \
     }                                                                          \
@@ -675,7 +797,7 @@ class operator_impl;
             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;                                                \
@@ -693,25 +815,27 @@ 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
@@ -811,9 +935,7 @@ class vtable<property<IsThrowing, HasStrongExceptGuarantee, FormalArgs...>> {
         }
       }
 
-      // TODO Use an unreachable intrinsic
-      assert(false && "Unreachable!");
-      std::exit(-1);
+      FU2_DETAIL_UNREACHABLE();
     }
 
     template <typename Box>
@@ -862,6 +984,9 @@ class vtable<property<IsThrowing, HasStrongExceptGuarantee, FormalArgs...>> {
         write_empty(to, true);
         break;
       }
+      default: {
+        FU2_DETAIL_UNREACHABLE();
+      }
     }
   }
 
@@ -916,13 +1041,13 @@ public:
 
   /// 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)...);
   }
@@ -952,26 +1077,28 @@ public:
 /// 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_;
@@ -979,13 +1106,14 @@ class internal_capacity_holder {
 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 {
@@ -999,7 +1127,7 @@ public:
 
 /// 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...>
@@ -1012,56 +1140,74 @@ class erasure : internal_capacity_holder<Config::capacity> {
 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(),
@@ -1069,7 +1215,7 @@ public:
     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());
@@ -1077,7 +1223,7 @@ public:
   }
 
   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());
@@ -1086,26 +1232,27 @@ public:
     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
@@ -1118,7 +1265,7 @@ public:
   /// 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,
@@ -1174,11 +1321,16 @@ public:
 
   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;
 
@@ -1208,11 +1360,19 @@ public:
   }
 
   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
@@ -1221,7 +1381,7 @@ public:
   }
 
   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)...);
   }
@@ -1234,13 +1394,12 @@ template <typename T, typename Signature,
           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>
@@ -1251,9 +1410,68 @@ struct accepts_all<
     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!");
 
@@ -1343,15 +1561,17 @@ public:
   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_) {
   }
 
@@ -1359,7 +1579,7 @@ public:
   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_)) {
   }
 
@@ -1369,7 +1589,8 @@ public:
             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,
@@ -1377,13 +1598,13 @@ public:
             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;
@@ -1415,7 +1636,7 @@ public:
             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;
   }
 
@@ -1441,9 +1662,9 @@ public:
             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
@@ -1488,70 +1709,104 @@ bool operator!=(std::nullptr_t, function<Config, Property> const& f) {
   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;
@@ -1575,7 +1830,20 @@ constexpr auto overload(T&&... callables) {
 }
 } // 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_