Ver Fonte

core: standalone any class

Michele Caini há 5 anos atrás
pai
commit
c95041d7c3

+ 73 - 0
docs/md/core.md

@@ -13,6 +13,7 @@
   * [Wide characters](wide-characters)
   * [Conflicts](#conflicts)
 * [Monostate](#monostate)
+* [Any as in any type](#any-as-in-any-type)
 * [Type support](#type-support)
   * [Type info](#type-info)
     * [Almost unique identifiers](#almost-unique-identifiers)
@@ -214,6 +215,78 @@ const bool b = entt::monostate<"mykey"_hs>{};
 const int i = entt::monostate<entt::hashed_string{"mykey"}>{};
 ```
 
+# Any as in any type
+
+`EnTT` comes with its own `any` type. It may seem redundant considering that
+C++17 introduced `std::any`, but it is not (hopefully).<br/>
+In fact, the _type_ returned by an `std::any` is a const reference to an
+`std::type_info`, an implementation defined class that's not something everyone
+wants to see in a software. Furthermore, there is no way to connect it with the
+type system of the library and therefore with its integrated RTTI support.<br/>
+Note that this class is largely used internally by the library itself.
+
+The API is very similar to that of its most famous counterpart, mainly because
+this class serves the same purpose of being an opaque container for any type of
+value.<br/>
+Instances of `any` also minimize the number of allocations by relying on a well
+known technique called _small buffer optimization_ and a fake vtable.
+
+Creating an object of the `any` type, whether empty or not, is trivial:
+
+```cpp
+// an empty container
+entt::any empty{};
+
+// a container for an int
+entt::any any{0};
+
+// in place construction
+entt::any in_place{std::in_place_type<int>, 42};
+```
+
+The `any` class takes the burden of destroying the contained element when
+required, regardless of the storage strategy used for the specific object.<br/>
+Furthermore, an instance of `any` is not tied to an actual type. Therefore, the
+wrapper will be reconfigured by assigning it an object of a different type than
+the one contained, so as to be able to handle the new instance.<br/>
+When in doubt about the type of object contained, the `type` member function of
+`any` returns an instance of `type_info` associated with its element, or an
+invalid `type_info` object if the container is empty.
+
+A particularly interesting feature of this class is that it can also be used as
+an opaque container for non-const unmanaged elements:
+
+```cpp
+int value;
+entt::any any{std::ref(value)};
+```
+
+In other words, whenever `any` intercepts a `reference_wrapper`, it acts as a
+reference to the original instance rather than making a copy of or moving it
+internally. The contained object is never destroyed and users must ensure that
+its lifetime exceeds that of the container.<br/>
+Similarly, it's possible to create non-owning copies of `any` from an existing
+object:
+
+```cpp
+// aliasing constructor
+entt::any ref = other.ref();
+```
+
+In this case, it doesn't matter if the starting container actually holds an
+object or acts already as a reference for unmanaged elements, the new instance
+thus created won't create copies and will only serve as a reference for the
+original item.<br/>
+It means that, starting from the example above, both `ref` and` other` will
+point to the same object, whether it's initially contained in `other` or already
+an unmanaged element.
+
+To cast an instance of `any` to a type, the library offers a set of `any_cast`
+functions in all respects similar to their most famous counterparts.<br/>
+The only difference is that, in the case of `EnTT`, these won't raise exceptions
+but will only cross an assert in debug mode, otherwise resulting in undefined
+behavior in case of misuse in release mode.
+
 # Type support
 
 `EnTT` provides some basic information about types of all kinds.<br/>

+ 229 - 0
src/entt/core/any.hpp

@@ -0,0 +1,229 @@
+#ifndef ENTT_CORE_ANY_HPP
+#define ENTT_CORE_ANY_HPP
+
+
+#include <functional>
+#include <new>
+#include <type_traits>
+#include <utility>
+#include "../config/config.h"
+#include "type_info.hpp"
+
+
+namespace entt {
+
+
+class any {
+    enum class operation { COPY, MOVE, DTOR, ADDR, REF, TYPE };
+
+    using storage_type = std::aligned_storage_t<sizeof(double[2]), alignof(double[2])>;
+    using vtable_type = void *(const operation, const any &, void *);
+
+    template<typename Type>
+    static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
+
+    template<typename Type>
+    static void * basic_vtable(const operation op, const any &from, void *to) {
+        if constexpr(std::is_void_v<Type>) {
+            return nullptr;
+        } else if constexpr(std::is_lvalue_reference_v<Type>) {
+            switch(op) {
+            case operation::REF:
+                static_cast<any *>(to)->vtable = from.vtable;
+                [[fallthrough]];
+            case operation::COPY:
+            case operation::MOVE:
+                static_cast<any *>(to)->instance = from.instance;
+                [[fallthrough]];
+            case operation::DTOR:
+                break;
+            case operation::ADDR:
+                return from.instance;
+            case operation::TYPE:
+                *static_cast<type_info *>(to) = type_id<std::remove_reference_t<Type>>();
+                break;
+            }
+        } else if constexpr(in_situ<Type>) {
+            auto *instance = const_cast<Type *>(std::launder(reinterpret_cast<const Type *>(&from.storage)));
+
+            switch(op) {
+            case operation::COPY:
+                new (&static_cast<any *>(to)->storage) Type{std::as_const(*instance)};
+                break;
+            case operation::MOVE:
+                new (&static_cast<any *>(to)->storage) Type{std::move(*instance)};
+                [[fallthrough]];
+            case operation::DTOR:
+                instance->~Type();
+                break;
+            case operation::ADDR:
+                return instance;
+            case operation::REF:
+                static_cast<any *>(to)->vtable = basic_vtable<std::add_lvalue_reference_t<Type>>;
+                static_cast<any *>(to)->instance = instance;
+                break;
+            case operation::TYPE:
+                *static_cast<type_info *>(to) = type_id<Type>();
+                break;
+            }
+        } else {
+            switch(op) {
+            case operation::COPY:
+                static_cast<any *>(to)->instance = new Type{std::as_const(*static_cast<Type *>(from.instance))};
+                break;
+            case operation::MOVE:
+                static_cast<any *>(to)->instance = from.instance;
+                break;
+            case operation::DTOR:
+                delete static_cast<Type *>(from.instance);
+                break;
+            case operation::ADDR:
+                return from.instance;
+            case operation::REF:
+                static_cast<any *>(to)->vtable = basic_vtable<std::add_lvalue_reference_t<Type>>;
+                static_cast<any *>(to)->instance = from.instance;
+                break;
+            case operation::TYPE:
+                *static_cast<type_info *>(to) = type_id<Type>();
+                break;
+            }
+        }
+
+        return nullptr;
+    }
+
+public:
+    /*! @brief Default constructor. */
+    any() ENTT_NOEXCEPT
+        : vtable{&basic_vtable<void>},
+          instance{}
+    {}
+
+    template<typename Type, typename... Args>
+    explicit any(std::in_place_type_t<Type>, [[maybe_unused]] Args &&... args)
+        : vtable{&basic_vtable<Type>},
+          instance{}
+    {
+        if constexpr(!std::is_void_v<Type>) {
+            if constexpr(in_situ<Type>) {
+                new (&storage) Type{std::forward<Args>(args)...};
+            } else {
+                instance = new Type{std::forward<Args>(args)...};
+            }
+        }
+    }
+
+    template<typename Type>
+    any(std::reference_wrapper<Type> value)
+        : vtable{&basic_vtable<std::add_lvalue_reference_t<Type>>},
+          instance{&value.get()}
+    {}
+
+    template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, any>>>
+    any(Type &&value)
+        : any{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)}
+    {}
+
+    any(const any &other)
+        : any{}
+    {
+        vtable = other.vtable;
+        vtable(operation::COPY, other, this);
+    }
+
+    any(any &&other) ENTT_NOEXCEPT
+        : any{}
+    {
+        vtable = std::exchange(other.vtable, &basic_vtable<void>);
+        vtable(operation::MOVE, other, this);
+    }
+
+    ~any() {
+        vtable(operation::DTOR, *this, nullptr);
+    }
+
+    any & operator=(any other) {
+        swap(*this, other);
+        return *this;
+    }
+
+    [[nodiscard]] const void * data() const ENTT_NOEXCEPT {
+        return vtable(operation::ADDR, *this, nullptr);
+    }
+
+    [[nodiscard]] void * data() ENTT_NOEXCEPT {
+        return const_cast<void *>(std::as_const(*this).data());
+    }
+
+    template<typename Type, typename... Args>
+    void emplace(Args &&... args) {
+        *this = any{std::in_place_type<Type>, std::forward<Args>(args)...};
+    }
+
+    [[nodiscard]] any ref() const ENTT_NOEXCEPT {
+        any other{};
+        vtable(operation::REF, *this, &other);
+        return other;
+    }
+
+    [[nodiscard]] type_info type() const ENTT_NOEXCEPT {
+        type_info info;
+        vtable(operation::TYPE, *this, &info);
+        return info;
+    }
+
+    [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
+        return !(vtable(operation::ADDR, *this, nullptr) == nullptr);
+    }
+
+    friend void swap(any &lhs, any &rhs) {
+        any tmp{};
+        lhs.vtable(operation::MOVE, lhs, &tmp);
+        rhs.vtable(operation::MOVE, rhs, &lhs);
+        lhs.vtable(operation::MOVE, tmp, &rhs);
+        std::swap(lhs.vtable, rhs.vtable);
+    }
+
+private:
+    vtable_type *vtable;
+    union { void *instance; storage_type storage; };
+};
+
+
+template<typename Type>
+Type any_cast(const any &any) ENTT_NOEXCEPT {
+    ENTT_ASSERT(any.type() == type_id<Type>());
+    return *static_cast<const Type *>(any.data());
+}
+
+
+template<typename Type>
+Type any_cast(any &any) ENTT_NOEXCEPT {
+    ENTT_ASSERT(any.type() == type_id<Type>());
+    return *static_cast<Type *>(any.data());
+}
+
+
+template<typename Type>
+Type any_cast(any &&any) ENTT_NOEXCEPT {
+    ENTT_ASSERT(any.type() == type_id<Type>());
+    return std::move(*static_cast<Type *>(any.data()));
+}
+
+
+template<typename Type>
+const Type * any_cast(const any *any) ENTT_NOEXCEPT {
+    return (any->type() == type_id<Type>() ? static_cast<const Type *>(any->data()) : nullptr);
+}
+
+
+template<typename Type>
+Type * any_cast(any *any) ENTT_NOEXCEPT {
+    return (any->type() == type_id<Type>() ? static_cast<Type *>(any->data()) : nullptr);
+}
+
+
+}
+
+
+#endif

+ 1 - 0
src/entt/entt.hpp

@@ -1,5 +1,6 @@
 #include "config/version.h"
 #include "core/algorithm.hpp"
+#include "core/any.hpp"
 #include "core/attribute.h"
 #include "core/family.hpp"
 #include "core/hashed_string.hpp"

+ 3 - 4
src/entt/meta/internal.hpp

@@ -38,8 +38,7 @@ class meta_storage {
     using vtable_type = void *(const operation, const meta_storage &, meta_storage *);
 
     template<typename Type>
-    static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type)
-        && std::is_nothrow_move_constructible_v<Type> && std::is_nothrow_copy_constructible_v<Type>;
+    static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
 
     template<typename Type>
     static void * basic_vtable(const operation op, const meta_storage &from, meta_storage *to) {
@@ -103,7 +102,6 @@ class meta_storage {
     }
 
 public:
-    /*! @brief Default constructor. */
     meta_storage() ENTT_NOEXCEPT
         : vtable{&basic_vtable<void>},
           instance{}
@@ -111,7 +109,8 @@ public:
 
     template<typename Type, typename... Args>
     explicit meta_storage(std::in_place_type_t<Type>, [[maybe_unused]] Args &&... args)
-        : vtable{&basic_vtable<Type>}
+        : vtable{&basic_vtable<Type>},
+          instance{}
     {
         if constexpr(!std::is_void_v<Type>) {
             if constexpr(in_situ<Type>) {

+ 1 - 0
test/CMakeLists.txt

@@ -153,6 +153,7 @@ endif()
 # Test core
 
 SETUP_BASIC_TEST(algorithm entt/core/algorithm.cpp)
+SETUP_BASIC_TEST(any entt/core/any.cpp)
 SETUP_BASIC_TEST(family entt/core/family.cpp)
 SETUP_BASIC_TEST(hashed_string entt/core/hashed_string.cpp)
 SETUP_BASIC_TEST(ident entt/core/ident.cpp)

+ 545 - 0
test/entt/core/any.cpp

@@ -0,0 +1,545 @@
+#include <algorithm>
+#include <gtest/gtest.h>
+#include <entt/core/any.hpp>
+
+struct fat {
+    double value[4];
+    inline static int counter = 0;
+
+    ~fat() { ++counter; }
+
+    bool operator==(const fat &other) const {
+        return std::equal(std::begin(value), std::end(value), std::begin(other.value), std::end(other.value));
+    }
+};
+
+struct empty {
+    inline static int counter = 0;
+    ~empty() { ++counter; }
+};
+
+TEST(Any, SBO) {
+    entt::any any{'c'};
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::type_id<char>());
+    ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<char>(any), 'c');
+}
+
+TEST(Any, NoSBO) {
+    fat instance{{.1, .2, .3, .4}};
+    entt::any any{instance};
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(any), instance);
+}
+
+TEST(Any, Empty) {
+    entt::any any{};
+
+    ASSERT_FALSE(any);
+    ASSERT_FALSE(any.type());
+    ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
+    ASSERT_EQ(any.data(), nullptr);
+}
+
+TEST(Any, SBOInPlaceTypeConstruction) {
+    entt::any any{std::in_place_type<int>, 42};
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<int>(any), 42);
+
+    auto other = any.ref();
+
+    ASSERT_TRUE(other);
+    ASSERT_EQ(other.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<int>(other), 42);
+    ASSERT_EQ(other.data(), any.data());
+}
+
+TEST(Any, SBOAsRefConstruction) {
+    int value = 42;
+    entt::any any{std::ref(value)};
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<int>(any), 42);
+    ASSERT_EQ(any.data(), &value);
+
+    auto other = any.ref();
+
+    ASSERT_TRUE(other);
+    ASSERT_EQ(other.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<int>(other), 42);
+    ASSERT_EQ(other.data(), any.data());
+}
+
+TEST(Any, SBOCopyConstruction) {
+    entt::any any{42};
+    entt::any other{any};
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(other);
+    ASSERT_EQ(any.type(), entt::type_id<int>());
+    ASSERT_EQ(other.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+    ASSERT_EQ(entt::any_cast<int>(other), 42);
+}
+
+TEST(Any, SBOCopyAssignment) {
+    entt::any any{42};
+    entt::any other{3};
+
+    other = any;
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(other);
+    ASSERT_EQ(any.type(), entt::type_id<int>());
+    ASSERT_EQ(other.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+    ASSERT_EQ(entt::any_cast<int>(other), 42);
+}
+
+TEST(Any, SBOMoveConstruction) {
+    entt::any any{42};
+    entt::any other{std::move(any)};
+
+    ASSERT_FALSE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(any.type());
+    ASSERT_EQ(other.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+    ASSERT_EQ(entt::any_cast<int>(other), 42);
+}
+
+TEST(Any, SBOMoveAssignment) {
+    entt::any any{42};
+    entt::any other{3};
+
+    other = std::move(any);
+
+    ASSERT_FALSE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(any.type());
+    ASSERT_EQ(other.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+    ASSERT_EQ(entt::any_cast<int>(other), 42);
+}
+
+TEST(Any, SBODirectAssignment) {
+    entt::any any{};
+    any = 42;
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<int>(any), 42);
+}
+
+TEST(Any, NoSBOInPlaceTypeConstruction) {
+    fat instance{{.1, .2, .3, .4}};
+    entt::any any{std::in_place_type<fat>, instance};
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(any), instance);
+
+    auto other = any.ref();
+
+    ASSERT_TRUE(other);
+    ASSERT_EQ(other.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<fat>(other), (fat{{.1, .2, .3, .4}}));
+    ASSERT_EQ(other.data(), any.data());
+}
+
+TEST(Any, NoSBOAsRefConstruction) {
+    fat instance{{.1, .2, .3, .4}};
+    entt::any any{std::ref(instance)};
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(any), instance);
+    ASSERT_EQ(any.data(), &instance);
+
+    auto other = any.ref();
+
+    ASSERT_TRUE(other);
+    ASSERT_EQ(other.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<fat>(other), (fat{{.1, .2, .3, .4}}));
+    ASSERT_EQ(other.data(), any.data());
+}
+
+TEST(Any, NoSBOCopyConstruction) {
+    fat instance{{.1, .2, .3, .4}};
+    entt::any any{instance};
+    entt::any other{any};
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(other);
+    ASSERT_EQ(any.type(), entt::type_id<fat>());
+    ASSERT_EQ(other.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(other), instance);
+}
+
+TEST(Any, NoSBOCopyAssignment) {
+    fat instance{{.1, .2, .3, .4}};
+    entt::any any{instance};
+    entt::any other{3};
+
+    other = any;
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(other);
+    ASSERT_EQ(any.type(), entt::type_id<fat>());
+    ASSERT_EQ(other.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(other), instance);
+}
+
+TEST(Any, NoSBOMoveConstruction) {
+    fat instance{{.1, .2, .3, .4}};
+    entt::any any{instance};
+    entt::any other{std::move(any)};
+
+    ASSERT_FALSE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(any.type());
+    ASSERT_EQ(other.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(other), instance);
+}
+
+TEST(Any, NoSBOMoveAssignment) {
+    fat instance{{.1, .2, .3, .4}};
+    entt::any any{instance};
+    entt::any other{3};
+
+    other = std::move(any);
+
+    ASSERT_FALSE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(any.type());
+    ASSERT_EQ(other.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(other), instance);
+}
+
+TEST(Any, NoSBODirectAssignment) {
+    fat instance{{.1, .2, .3, .4}};
+    entt::any any{};
+    any = instance;
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(any), instance);
+}
+
+TEST(Any, VoidInPlaceTypeConstruction) {
+    entt::any any{std::in_place_type<void>};
+
+    ASSERT_FALSE(any);
+    ASSERT_FALSE(any.type());
+    ASSERT_EQ(entt::any_cast<int>(&any), nullptr);
+}
+
+TEST(Any, VoidCopyConstruction) {
+    entt::any any{std::in_place_type<void>};
+    entt::any other{any};
+
+    ASSERT_FALSE(any);
+    ASSERT_FALSE(other);
+    ASSERT_FALSE(any.type());
+    ASSERT_FALSE(other.type());
+    ASSERT_EQ(entt::any_cast<int>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+}
+
+TEST(Any, VoidCopyAssignment) {
+    entt::any any{std::in_place_type<void>};
+    entt::any other{std::in_place_type<void>};
+
+    other = any;
+
+    ASSERT_FALSE(any);
+    ASSERT_FALSE(other);
+    ASSERT_FALSE(any.type());
+    ASSERT_FALSE(other.type());
+    ASSERT_EQ(entt::any_cast<int>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+}
+
+TEST(Any, VoidMoveConstruction) {
+    entt::any any{std::in_place_type<void>};
+    entt::any other{std::move(any)};
+
+    ASSERT_FALSE(any);
+    ASSERT_FALSE(other);
+    ASSERT_FALSE(any.type());
+    ASSERT_FALSE(other.type());
+    ASSERT_EQ(entt::any_cast<int>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+}
+
+TEST(Any, VoidMoveAssignment) {
+    entt::any any{std::in_place_type<void>};
+    entt::any other{std::in_place_type<void>};
+
+    other = std::move(any);
+
+    ASSERT_FALSE(any);
+    ASSERT_FALSE(other);
+    ASSERT_FALSE(any.type());
+    ASSERT_FALSE(other.type());
+    ASSERT_EQ(entt::any_cast<int>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
+}
+
+TEST(Any, SBOMoveInvalidate) {
+    entt::any any{42};
+    entt::any other{std::move(any)};
+    entt::any valid = std::move(other);
+
+    ASSERT_FALSE(any);
+    ASSERT_FALSE(other);
+    ASSERT_TRUE(valid);
+}
+
+TEST(Any, NoSBOMoveInvalidate) {
+    fat instance{{.1, .2, .3, .4}};
+    entt::any any{instance};
+    entt::any other{std::move(any)};
+    entt::any valid = std::move(other);
+
+    ASSERT_FALSE(any);
+    ASSERT_FALSE(other);
+    ASSERT_TRUE(valid);
+}
+
+TEST(Any, VoidMoveInvalidate) {
+    entt::any any{std::in_place_type<void>};
+    entt::any other{std::move(any)};
+    entt::any valid = std::move(other);
+
+    ASSERT_FALSE(any);
+    ASSERT_FALSE(other);
+    ASSERT_FALSE(valid);
+}
+
+TEST(Any, SBODestruction) {
+    {
+        entt::any any{empty{}};
+        empty::counter = 0;
+    }
+
+    ASSERT_EQ(empty::counter, 1);
+}
+
+TEST(Any, NoSBODestruction) {
+    {
+        entt::any any{fat{}};
+        fat::counter = 0;
+    }
+
+    ASSERT_EQ(fat::counter, 1);
+}
+
+TEST(Any, VoidDestruction) {
+    // just let asan tell us if everything is ok here
+    [[maybe_unused]] entt::any any{std::in_place_type<void>};
+}
+
+TEST(Any, Emplace) {
+    entt::any any{};
+    any.emplace<int>(42);
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
+    ASSERT_EQ(entt::any_cast<int>(any), 42);
+}
+
+TEST(Any, EmplaceVoid) {
+    entt::any any{};
+    any.emplace<void>();
+
+    ASSERT_FALSE(any);
+    ASSERT_FALSE(any.type());
+ }
+
+TEST(Any, SBOSwap) {
+    entt::any lhs{'c'};
+    entt::any rhs{42};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_EQ(lhs.type(), entt::type_id<int>());
+    ASSERT_EQ(rhs.type(), entt::type_id<char>());
+    ASSERT_EQ(entt::any_cast<char>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<int>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<int>(lhs), 42);
+    ASSERT_EQ(entt::any_cast<char>(rhs), 'c');
+}
+
+TEST(Any, NoSBOSwap) {
+    entt::any lhs{fat{{.1, .2, .3, .4}}};
+    entt::any rhs{fat{{.4, .3, .2, .1}}};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_EQ(entt::any_cast<fat>(lhs), (fat{{.4, .3, .2, .1}}));
+    ASSERT_EQ(entt::any_cast<fat>(rhs), (fat{{.1, .2, .3, .4}}));
+}
+
+TEST(Any, VoidSwap) {
+    entt::any lhs{std::in_place_type<void>};
+    entt::any rhs{std::in_place_type<void>};
+    const auto *pre = lhs.data();
+
+    std::swap(lhs, rhs);
+
+    ASSERT_EQ(pre, lhs.data());
+}
+
+TEST(Any, SBOWithNoSBOSwap) {
+    entt::any lhs{fat{{.1, .2, .3, .4}}};
+    entt::any rhs{'c'};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_EQ(lhs.type(), entt::type_id<char>());
+    ASSERT_EQ(rhs.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<fat>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<char>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<char>(lhs), 'c');
+    ASSERT_EQ(entt::any_cast<fat>(rhs), (fat{{.1, .2, .3, .4}}));
+}
+
+TEST(Any, SBOWithRefSwap) {
+    int value = 3;
+    entt::any lhs{std::ref(value)};
+    entt::any rhs{'c'};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_EQ(lhs.type(), entt::type_id<char>());
+    ASSERT_EQ(rhs.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<int>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<char>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<char>(lhs), 'c');
+    ASSERT_EQ(entt::any_cast<int>(rhs), 3);
+    ASSERT_EQ(rhs.data(), &value);
+}
+
+TEST(Any, SBOWithEmptySwap) {
+    entt::any lhs{'c'};
+    entt::any rhs{};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_FALSE(lhs);
+    ASSERT_EQ(rhs.type(), entt::type_id<char>());
+    ASSERT_EQ(entt::any_cast<char>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<double>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<char>(rhs), 'c');
+
+    std::swap(lhs, rhs);
+
+    ASSERT_FALSE(rhs);
+    ASSERT_EQ(lhs.type(), entt::type_id<char>());
+    ASSERT_EQ(entt::any_cast<double>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<char>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<char>(lhs), 'c');
+}
+
+TEST(Any, SBOWithVoidSwap) {
+    entt::any lhs{'c'};
+    entt::any rhs{std::in_place_type<void>};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_FALSE(lhs);
+    ASSERT_EQ(rhs.type(), entt::type_id<char>());
+    ASSERT_EQ(entt::any_cast<char>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<double>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<char>(rhs), 'c');
+
+    std::swap(lhs, rhs);
+
+    ASSERT_FALSE(rhs);
+    ASSERT_EQ(lhs.type(), entt::type_id<char>());
+    ASSERT_EQ(entt::any_cast<double>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<char>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<char>(lhs), 'c');
+}
+
+TEST(Any, NoSBOWithRefSwap) {
+    int value = 3;
+    entt::any lhs{std::ref(value)};
+    entt::any rhs{fat{{.1, .2, .3, .4}}};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_EQ(lhs.type(), entt::type_id<fat>());
+    ASSERT_EQ(rhs.type(), entt::type_id<int>());
+    ASSERT_EQ(entt::any_cast<int>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(lhs), (fat{{.1, .2, .3, .4}}));
+    ASSERT_EQ(entt::any_cast<int>(rhs), 3);
+    ASSERT_EQ(rhs.data(), &value);
+}
+
+TEST(Any, NoSBOWithEmptySwap) {
+    entt::any lhs{fat{{.1, .2, .3, .4}}};
+    entt::any rhs{};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_FALSE(lhs);
+    ASSERT_EQ(rhs.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<fat>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<double>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(rhs), (fat{{.1, .2, .3, .4}}));
+
+    std::swap(lhs, rhs);
+
+    ASSERT_FALSE(rhs);
+    ASSERT_EQ(lhs.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<double>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(lhs), (fat{{.1, .2, .3, .4}}));
+}
+
+TEST(Any, NoSBOWithVoidSwap) {
+    entt::any lhs{fat{{.1, .2, .3, .4}}};
+    entt::any rhs{std::in_place_type<void>};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_FALSE(lhs);
+    ASSERT_EQ(rhs.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<fat>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<double>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(rhs), (fat{{.1, .2, .3, .4}}));
+
+    std::swap(lhs, rhs);
+
+    ASSERT_FALSE(rhs);
+    ASSERT_EQ(lhs.type(), entt::type_id<fat>());
+    ASSERT_EQ(entt::any_cast<double>(&lhs), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(&rhs), nullptr);
+    ASSERT_EQ(entt::any_cast<fat>(lhs), (fat{{.1, .2, .3, .4}}));
+}
+
+TEST(Any, AnyCastTemporary) {
+    ASSERT_EQ(entt::any_cast<int>(42), 42);
+}

+ 1 - 0
test/entt/meta/meta_any.cpp

@@ -35,6 +35,7 @@ struct fat_t: empty_t {
 
     int *foo{nullptr};
     int *bar{nullptr};
+    double gnam[4];
 };
 
 struct not_comparable_t {