diff --git a/docs/md/core.md b/docs/md/core.md index 7600f2b72..0837618ff 100644 --- a/docs/md/core.md +++ b/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{}; ``` +# 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).
+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.
+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.
+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, 42}; +``` + +The `any` class takes the burden of destroying the contained element when +required, regardless of the storage strategy used for the specific object.
+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.
+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.
+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.
+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.
+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.
diff --git a/src/entt/core/any.hpp b/src/entt/core/any.hpp new file mode 100644 index 000000000..73962ca2a --- /dev/null +++ b/src/entt/core/any.hpp @@ -0,0 +1,229 @@ +#ifndef ENTT_CORE_ANY_HPP +#define ENTT_CORE_ANY_HPP + + +#include +#include +#include +#include +#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; + using vtable_type = void *(const operation, const any &, void *); + + template + static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v; + + template + static void * basic_vtable(const operation op, const any &from, void *to) { + if constexpr(std::is_void_v) { + return nullptr; + } else if constexpr(std::is_lvalue_reference_v) { + switch(op) { + case operation::REF: + static_cast(to)->vtable = from.vtable; + [[fallthrough]]; + case operation::COPY: + case operation::MOVE: + static_cast(to)->instance = from.instance; + [[fallthrough]]; + case operation::DTOR: + break; + case operation::ADDR: + return from.instance; + case operation::TYPE: + *static_cast(to) = type_id>(); + break; + } + } else if constexpr(in_situ) { + auto *instance = const_cast(std::launder(reinterpret_cast(&from.storage))); + + switch(op) { + case operation::COPY: + new (&static_cast(to)->storage) Type{std::as_const(*instance)}; + break; + case operation::MOVE: + new (&static_cast(to)->storage) Type{std::move(*instance)}; + [[fallthrough]]; + case operation::DTOR: + instance->~Type(); + break; + case operation::ADDR: + return instance; + case operation::REF: + static_cast(to)->vtable = basic_vtable>; + static_cast(to)->instance = instance; + break; + case operation::TYPE: + *static_cast(to) = type_id(); + break; + } + } else { + switch(op) { + case operation::COPY: + static_cast(to)->instance = new Type{std::as_const(*static_cast(from.instance))}; + break; + case operation::MOVE: + static_cast(to)->instance = from.instance; + break; + case operation::DTOR: + delete static_cast(from.instance); + break; + case operation::ADDR: + return from.instance; + case operation::REF: + static_cast(to)->vtable = basic_vtable>; + static_cast(to)->instance = from.instance; + break; + case operation::TYPE: + *static_cast(to) = type_id(); + break; + } + } + + return nullptr; + } + +public: + /*! @brief Default constructor. */ + any() ENTT_NOEXCEPT + : vtable{&basic_vtable}, + instance{} + {} + + template + explicit any(std::in_place_type_t, [[maybe_unused]] Args &&... args) + : vtable{&basic_vtable}, + instance{} + { + if constexpr(!std::is_void_v) { + if constexpr(in_situ) { + new (&storage) Type{std::forward(args)...}; + } else { + instance = new Type{std::forward(args)...}; + } + } + } + + template + any(std::reference_wrapper value) + : vtable{&basic_vtable>}, + instance{&value.get()} + {} + + template>, any>>> + any(Type &&value) + : any{std::in_place_type>>, std::forward(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); + 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(std::as_const(*this).data()); + } + + template + void emplace(Args &&... args) { + *this = any{std::in_place_type, std::forward(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 +Type any_cast(const any &any) ENTT_NOEXCEPT { + ENTT_ASSERT(any.type() == type_id()); + return *static_cast(any.data()); +} + + +template +Type any_cast(any &any) ENTT_NOEXCEPT { + ENTT_ASSERT(any.type() == type_id()); + return *static_cast(any.data()); +} + + +template +Type any_cast(any &&any) ENTT_NOEXCEPT { + ENTT_ASSERT(any.type() == type_id()); + return std::move(*static_cast(any.data())); +} + + +template +const Type * any_cast(const any *any) ENTT_NOEXCEPT { + return (any->type() == type_id() ? static_cast(any->data()) : nullptr); +} + + +template +Type * any_cast(any *any) ENTT_NOEXCEPT { + return (any->type() == type_id() ? static_cast(any->data()) : nullptr); +} + + +} + + +#endif diff --git a/src/entt/entt.hpp b/src/entt/entt.hpp index c1dfad2f7..b336131b3 100644 --- a/src/entt/entt.hpp +++ b/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" diff --git a/src/entt/meta/internal.hpp b/src/entt/meta/internal.hpp index d97fe865a..4c6e1204c 100644 --- a/src/entt/meta/internal.hpp +++ b/src/entt/meta/internal.hpp @@ -38,8 +38,7 @@ class meta_storage { using vtable_type = void *(const operation, const meta_storage &, meta_storage *); template - static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) - && std::is_nothrow_move_constructible_v && std::is_nothrow_copy_constructible_v; + static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v; template 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}, instance{} @@ -111,7 +109,8 @@ public: template explicit meta_storage(std::in_place_type_t, [[maybe_unused]] Args &&... args) - : vtable{&basic_vtable} + : vtable{&basic_vtable}, + instance{} { if constexpr(!std::is_void_v) { if constexpr(in_situ) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 246a49acf..ac584c9f2 100644 --- a/test/CMakeLists.txt +++ b/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) diff --git a/test/entt/core/any.cpp b/test/entt/core/any.cpp new file mode 100644 index 000000000..226245cf6 --- /dev/null +++ b/test/entt/core/any.cpp @@ -0,0 +1,545 @@ +#include +#include +#include + +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()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), instance); +} + +TEST(Any, Empty) { + entt::any any{}; + + ASSERT_FALSE(any); + ASSERT_FALSE(any.type()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(any.data(), nullptr); +} + +TEST(Any, SBOInPlaceTypeConstruction) { + entt::any any{std::in_place_type, 42}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), 42); + + auto other = any.ref(); + + ASSERT_TRUE(other); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(any.data(), &value); + + auto other = any.ref(); + + ASSERT_TRUE(other); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(other), 42); +} + +TEST(Any, SBODirectAssignment) { + entt::any any{}; + any = 42; + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), 42); +} + +TEST(Any, NoSBOInPlaceTypeConstruction) { + fat instance{{.1, .2, .3, .4}}; + entt::any any{std::in_place_type, instance}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), instance); + + auto other = any.ref(); + + ASSERT_TRUE(other); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), instance); + ASSERT_EQ(any.data(), &instance); + + auto other = any.ref(); + + ASSERT_TRUE(other); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), instance); +} + +TEST(Any, VoidInPlaceTypeConstruction) { + entt::any any{std::in_place_type}; + + ASSERT_FALSE(any); + ASSERT_FALSE(any.type()); + ASSERT_EQ(entt::any_cast(&any), nullptr); +} + +TEST(Any, VoidCopyConstruction) { + entt::any any{std::in_place_type}; + entt::any other{any}; + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_FALSE(any.type()); + ASSERT_FALSE(other.type()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&other), nullptr); +} + +TEST(Any, VoidCopyAssignment) { + entt::any any{std::in_place_type}; + entt::any other{std::in_place_type}; + + other = any; + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_FALSE(any.type()); + ASSERT_FALSE(other.type()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&other), nullptr); +} + +TEST(Any, VoidMoveConstruction) { + entt::any any{std::in_place_type}; + 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(&any), nullptr); + ASSERT_EQ(entt::any_cast(&other), nullptr); +} + +TEST(Any, VoidMoveAssignment) { + entt::any any{std::in_place_type}; + entt::any other{std::in_place_type}; + + other = std::move(any); + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_FALSE(any.type()); + ASSERT_FALSE(other.type()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&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}; + 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}; +} + +TEST(Any, Emplace) { + entt::any any{}; + any.emplace(42); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), 42); +} + +TEST(Any, EmplaceVoid) { + entt::any any{}; + any.emplace(); + + 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()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), 42); + ASSERT_EQ(entt::any_cast(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(lhs), (fat{{.4, .3, .2, .1}})); + ASSERT_EQ(entt::any_cast(rhs), (fat{{.1, .2, .3, .4}})); +} + +TEST(Any, VoidSwap) { + entt::any lhs{std::in_place_type}; + entt::any rhs{std::in_place_type}; + 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()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), 'c'); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), 'c'); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(rhs), 'c'); + + std::swap(lhs, rhs); + + ASSERT_FALSE(rhs); + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), 'c'); +} + +TEST(Any, SBOWithVoidSwap) { + entt::any lhs{'c'}; + entt::any rhs{std::in_place_type}; + + std::swap(lhs, rhs); + + ASSERT_FALSE(lhs); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(rhs), 'c'); + + std::swap(lhs, rhs); + + ASSERT_FALSE(rhs); + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), (fat{{.1, .2, .3, .4}})); + ASSERT_EQ(entt::any_cast(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()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(rhs), (fat{{.1, .2, .3, .4}})); + + std::swap(lhs, rhs); + + ASSERT_FALSE(rhs); + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), (fat{{.1, .2, .3, .4}})); +} + +TEST(Any, NoSBOWithVoidSwap) { + entt::any lhs{fat{{.1, .2, .3, .4}}}; + entt::any rhs{std::in_place_type}; + + std::swap(lhs, rhs); + + ASSERT_FALSE(lhs); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(rhs), (fat{{.1, .2, .3, .4}})); + + std::swap(lhs, rhs); + + ASSERT_FALSE(rhs); + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), (fat{{.1, .2, .3, .4}})); +} + +TEST(Any, AnyCastTemporary) { + ASSERT_EQ(entt::any_cast(42), 42); +} diff --git a/test/entt/meta/meta_any.cpp b/test/entt/meta/meta_any.cpp index bf7648ea3..615485a3f 100644 --- a/test/entt/meta/meta_any.cpp +++ b/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 {