diff --git a/src/entt/meta/meta.hpp b/src/entt/meta/meta.hpp index 6f49f0e34..1185dd4f1 100644 --- a/src/entt/meta/meta.hpp +++ b/src/entt/meta/meta.hpp @@ -303,75 +303,111 @@ class meta_any { friend class meta_handle; using storage_type = std::aligned_storage_t; - using compare_fn_type = bool(const void *, const void *) ENTT_NOEXCEPT; + using compare_fn_type = bool(const void *, const void *); using copy_fn_type = void *(storage_type &, const void *); using destroy_fn_type = void(storage_type &); - using steal_fn_type = void *(storage_type &, storage_type &, destroy_fn_type *, void *) ENTT_NOEXCEPT; + using steal_fn_type = void *(storage_type &, storage_type &, destroy_fn_type *, void *) ENTT_NOEXCEPT; + + template> + struct type_traits { + using chunk_type = std::aligned_storage_t; + + template + static void * instance(storage_type &storage, Args &&... args) { + auto chunk = std::make_unique(); + auto *instance = new (chunk.get()) Type{std::forward(args)...}; + new (&storage) chunk_type *{chunk.get()}; + chunk.release(); + return instance; + } + + static void destroy(storage_type &storage) { + auto *node = internal::meta_info::resolve(); + auto *chunk = *reinterpret_cast(&storage); + auto *instance = reinterpret_cast(chunk); + node->dtor ? node->dtor->invoke(*instance) : node->destroy(*instance); + delete chunk; + } + + static void * copy(storage_type &storage, const void *instance) { + auto chunk = std::make_unique(); + new (&storage) chunk_type *{chunk.get()}; + auto *other = new (chunk.get()) Type{*static_cast(instance)}; + chunk.release(); + return other; + } + + static void * steal(storage_type &to, storage_type &from, destroy_fn_type *, void *instance) ENTT_NOEXCEPT { + auto *chunk = *reinterpret_cast(&from); + new (&to) chunk_type *{chunk}; + chunk->~chunk_type(); + return instance; + } + + static bool compare(const void *lhs, const void *rhs) { + return meta_any::compare(0, *static_cast(lhs), *static_cast(rhs)); + } + }; template - static auto compare(int, const Type &lhs, const Type &rhs) ENTT_NOEXCEPT - -> decltype(lhs == rhs, bool{}) { + struct type_traits>> { + static void * instance(storage_type &) { + return nullptr; + } + + static void destroy(storage_type &) {} + + static void * copy(storage_type &, const void *) { + return nullptr; + } + + static void * steal(storage_type &, storage_type &, destroy_fn_type *, void *) ENTT_NOEXCEPT { + return nullptr; + } + + static bool compare(const void *, const void *) { + return true; + } + }; + + template + struct type_traits>> { + template + static void * instance(storage_type &storage, Args &&... args) { + return new (&storage) Type{std::forward(args)...}; + } + + static void destroy(storage_type &storage) { + auto *node = internal::meta_info::resolve(); + auto *instance = reinterpret_cast(&storage); + node->dtor ? node->dtor->invoke(*instance) : node->destroy(*instance); + } + + static void * copy(storage_type &storage, const void *instance) { + return new (&storage) Type{*static_cast(instance)}; + } + + static void * steal(storage_type &to, storage_type &from, destroy_fn_type *destroy_fn, void *) ENTT_NOEXCEPT { + void *instance = new (&to) Type{std::move(*reinterpret_cast(&from))}; + destroy_fn(from); + return instance; + } + + static bool compare(const void *lhs, const void *rhs) { + return meta_any::compare(0, *static_cast(lhs), *static_cast(rhs)); + } + }; + + template + static auto compare(int, const Type &lhs, const Type &rhs) -> decltype(lhs == rhs, bool{}) { return lhs == rhs; } template - static bool compare(char, const Type &lhs, const Type &rhs) ENTT_NOEXCEPT { + static bool compare(char, const Type &lhs, const Type &rhs) { return &lhs == &rhs; } - template - static bool compare(const void *lhs, const void *rhs) ENTT_NOEXCEPT { - return compare(0, *static_cast(lhs), *static_cast(rhs)); - } - - template - static void * copy_storage(storage_type &storage, const void *instance) { - return new (&storage) Type{*static_cast(instance)}; - } - - template - static void * copy_object(storage_type &storage, const void *instance) { - using chunk_type = std::aligned_storage_t; - auto chunk = std::make_unique(); - new (&storage) chunk_type *{chunk.get()}; - auto *other = new (chunk.get()) Type{*static_cast(instance)}; - chunk.release(); - return other; - } - - template - static void * steal_storage(storage_type &to, storage_type &from, destroy_fn_type *destroy_fn, void *) noexcept { - void *instance = new (&to) Type{std::move(*reinterpret_cast(&from))}; - destroy_fn(from); - return instance; - } - - template - static void * steal_object(storage_type &to, storage_type &from, destroy_fn_type *, void *instance) noexcept { - using chunk_type = std::aligned_storage_t; - auto *chunk = *reinterpret_cast(&from); - new (&to) chunk_type *{chunk}; - chunk->~chunk_type(); - return instance; - } - - template - static void destroy_storage(storage_type &storage) { - auto *node = internal::meta_info::resolve(); - auto *instance = reinterpret_cast(&storage); - node->dtor ? node->dtor->invoke(*instance) : node->destroy(*instance); - } - - template - static void destroy_object(storage_type &storage) { - using chunk_type = std::aligned_storage_t; - auto *node = internal::meta_info::resolve(); - auto *chunk = *reinterpret_cast(&storage); - auto *instance = reinterpret_cast(chunk); - node->dtor ? node->dtor->invoke(*instance) : node->destroy(*instance); - delete chunk; - } - public: /*! @brief Default constructor. */ meta_any() ENTT_NOEXCEPT @@ -401,30 +437,14 @@ public: template meta_any(std::in_place_type_t, Args &&... args) { using actual_type = std::remove_cv_t>; + using traits_type = type_traits; + node = internal::meta_info::resolve(); - - compare_fn = &compare; - - constexpr auto sbo_allowed = sizeof(actual_type) <= sizeof(void *) - && std::is_nothrow_move_constructible_v; - - if constexpr(sbo_allowed) { - instance = new (&storage) actual_type{std::forward(args)...}; - destroy_fn = &destroy_storage; - copy_fn = ©_storage; - steal_fn = &steal_storage; - } else { - using chunk_type = std::aligned_storage_t; - - auto chunk = std::make_unique(); - instance = new (chunk.get()) actual_type{std::forward(args)...}; - new (&storage) chunk_type *{chunk.get()}; - chunk.release(); - - destroy_fn = &destroy_object; - copy_fn = ©_object; - steal_fn = &steal_object; - } + instance = traits_type::instance(storage, std::forward(args)...); + destroy_fn = &traits_type::destroy; + copy_fn = &traits_type::copy; + steal_fn = &traits_type::steal; + compare_fn = &traits_type::compare; } /** @@ -622,7 +642,7 @@ public: * @return False if the container is empty, true otherwise. */ explicit operator bool() const ENTT_NOEXCEPT { - return instance; + return destroy_fn; } /** @@ -632,7 +652,7 @@ public: * otherwise. */ bool operator==(const meta_any &other) const ENTT_NOEXCEPT { - return (!instance && !other.instance) || (instance && other.instance && node == other.node && compare_fn(instance, other.instance)); + return (!compare_fn && !other.compare_fn) || (compare_fn && other.compare_fn && node == other.node && compare_fn(instance, other.instance)); } /** diff --git a/test/entt/meta/meta.cpp b/test/entt/meta/meta.cpp index c6f232444..7f21a5459 100644 --- a/test/entt/meta/meta.cpp +++ b/test/entt/meta/meta.cpp @@ -425,6 +425,61 @@ TEST_F(Meta, MetaAnyNoSBOMoveAssignment) { ASSERT_NE(other, fat_type{}); } +TEST_F(Meta, MetaAnyVoidInPlaceConstruction) { + entt::meta_any any{std::in_place_type}; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.can_cast()); + ASSERT_FALSE(any.can_cast()); + ASSERT_EQ(any.data(), nullptr); + ASSERT_EQ(std::as_const(any).data(), nullptr); + ASSERT_EQ(any, entt::meta_any{std::in_place_type}); +} + +TEST_F(Meta, MetaAnyVoidCopyConstruction) { + entt::meta_any any{std::in_place_type}; + entt::meta_any other{any}; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(other.can_cast()); + ASSERT_EQ(other, entt::meta_any{std::in_place_type}); +} + +TEST_F(Meta, MetaAnyVoidCopyAssignment) { + entt::meta_any any{std::in_place_type}; + entt::meta_any other{std::in_place_type}; + + other = any; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(other.can_cast()); + ASSERT_EQ(other, entt::meta_any{std::in_place_type}); +} + +TEST_F(Meta, MetaAnyVoidMoveConstruction) { + entt::meta_any any{std::in_place_type}; + entt::meta_any other{std::move(any)}; + + ASSERT_FALSE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(other.can_cast()); + ASSERT_EQ(other, entt::meta_any{std::in_place_type}); +} + +TEST_F(Meta, MetaAnyVoidMoveAssignment) { + entt::meta_any any{std::in_place_type}; + entt::meta_any other{std::in_place_type}; + + other = std::move(any); + + ASSERT_FALSE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(other.can_cast()); + ASSERT_EQ(other, entt::meta_any{std::in_place_type}); +} + TEST_F(Meta, MetaAnySBOMoveInvalidate) { entt::meta_any any{42}; entt::meta_any other{std::move(any)}; @@ -447,6 +502,16 @@ TEST_F(Meta, MetaAnyNoSBOMoveInvalidate) { ASSERT_TRUE(valid); } +TEST_F(Meta, MetaAnyVoidMoveInvalidate) { + entt::meta_any any{std::in_place_type}; + entt::meta_any other{std::move(any)}; + entt::meta_any valid = std::move(other); + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_TRUE(valid); +} + TEST_F(Meta, MetaAnySBODestruction) { ASSERT_EQ(empty_type::counter, 0); { entt::meta_any any{empty_type{}}; } @@ -459,6 +524,11 @@ TEST_F(Meta, MetaAnyNoSBODestruction) { ASSERT_EQ(fat_type::counter, 1); } +TEST_F(Meta, MetaAnyVoidDestruction) { + // just let asan tell us if everything is ok here + [[maybe_unused]] entt::meta_any any{std::in_place_type}; +} + TEST_F(Meta, MetaAnyEmplace) { entt::meta_any any{}; any.emplace(42); @@ -475,6 +545,17 @@ TEST_F(Meta, MetaAnyEmplace) { ASSERT_NE(any, entt::meta_any{3}); } +TEST_F(Meta, MetaAnyEmplaceVoid) { + entt::meta_any any{}; + any.emplace(); + + ASSERT_TRUE(any); + ASSERT_TRUE(any.can_cast()); + ASSERT_EQ(any.data(), nullptr); + ASSERT_EQ(std::as_const(any).data(), nullptr); + ASSERT_EQ(any, (entt::meta_any{std::in_place_type})); +} + TEST_F(Meta, MetaAnySBOSwap) { entt::meta_any lhs{'c'}; entt::meta_any rhs{42}; @@ -498,6 +579,16 @@ TEST_F(Meta, MetaAnyNoSBOSwap) { ASSERT_EQ(rhs.cast().bar, &i); } +TEST_F(Meta, MetaAnyVoidSwap) { + entt::meta_any lhs{std::in_place_type}; + entt::meta_any rhs{std::in_place_type}; + const auto *pre = lhs.data(); + + std::swap(lhs, rhs); + + ASSERT_EQ(pre, lhs.data()); +} + TEST_F(Meta, MetaAnySBOWithNoSBOSwap) { int value = 42; entt::meta_any lhs{fat_type{&value}}; @@ -529,6 +620,17 @@ TEST_F(Meta, MetaAnySBOWithEmptySwap) { ASSERT_EQ(lhs.cast(), 'c'); } +TEST_F(Meta, MetaAnySBOWithVoidSwap) { + entt::meta_any lhs{'c'}; + entt::meta_any rhs{std::in_place_type}; + + std::swap(lhs, rhs); + + ASSERT_TRUE(lhs.can_cast()); + ASSERT_TRUE(rhs.can_cast()); + ASSERT_EQ(rhs.cast(), 'c'); +} + TEST_F(Meta, MetaAnyNoSBOWithEmptySwap) { int i; entt::meta_any lhs{fat_type{&i}}; @@ -543,6 +645,20 @@ TEST_F(Meta, MetaAnyNoSBOWithEmptySwap) { ASSERT_EQ(lhs.cast().bar, &i); } +TEST_F(Meta, MetaAnyNoSBOWithVoidSwap) { + int i; + entt::meta_any lhs{fat_type{&i}}; + entt::meta_any rhs{std::in_place_type}; + + std::swap(lhs, rhs); + + ASSERT_EQ(rhs.cast().bar, &i); + + std::swap(lhs, rhs); + + ASSERT_EQ(lhs.cast().bar, &i); +} + TEST_F(Meta, MetaAnyComparable) { entt::meta_any any{'c'}; @@ -570,6 +686,21 @@ TEST_F(Meta, MetaAnyNotComparable) { ASSERT_TRUE(any != entt::meta_any{}); } +TEST_F(Meta, MetaAnyCompareVoid) { + entt::meta_any any{std::in_place_type}; + + ASSERT_EQ(any, any); + ASSERT_EQ(any, entt::meta_any{std::in_place_type}); + ASSERT_NE(any, entt::meta_any{'a'}); + ASSERT_NE(any, entt::meta_any{}); + + ASSERT_TRUE(any == any); + ASSERT_TRUE(any == entt::meta_any{std::in_place_type}); + ASSERT_FALSE(any == entt::meta_any{'a'}); + ASSERT_TRUE(any != entt::meta_any{'a'}); + ASSERT_TRUE(any != entt::meta_any{}); +} + TEST_F(Meta, MetaAnyCast) { entt::meta_any any{derived_type{}}; entt::meta_handle handle{any};