From 03e363f5d9bb1f7b26cc2041017e6e48e94c2155 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Wed, 22 Sep 2021 16:19:25 +0200 Subject: [PATCH] any: added the ::assign function to copy/move assign the wrapped variable --- docs/md/core.md | 31 ++++-- src/entt/core/any.hpp | 58 +++++++++-- test/entt/core/any.cpp | 226 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 289 insertions(+), 26 deletions(-) diff --git a/docs/md/core.md b/docs/md/core.md index 58ca50cb7..de1f70ad7 100644 --- a/docs/md/core.md +++ b/docs/md/core.md @@ -262,11 +262,26 @@ 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.
+the one contained, so as to be able to handle the new instance. + +There exists also a way to directly assign a value to the variable contained by +an `entt::any`, without necessarily replacing it. This is especially useful when +the object is used in _aliasing mode_, as described below: + +```cpp +entt::any any{42}; +any.assign(3); +``` + +The `any` class will also perform a check on the type information and whether or +not the original type was copy or move assignable, as appropriate.
+In all cases, the `assign` function returns a boolean value to indicate the +success or failure of the operation. + 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. The type is also used -internally when comparing two `any` objects: +`any` returns a const reference to the `type_info` associated with its element, +or `type_id()` if the container is empty. The type is also used internally +when comparing two `any` objects: ```cpp if(any == empty) { /* ... */ } @@ -274,7 +289,7 @@ if(any == empty) { /* ... */ } In this case, before proceeding with a comparison, it's verified that the _type_ of the two objects is actually the same.
-Refer to the `EnTT` type system documentation for more details on how +Refer to the `EnTT` type system documentation for more details about how `type_info` works and on possible risks of a comparison. A particularly interesting feature of this class is that it can also be used as @@ -382,7 +397,7 @@ information to that provided by its counterpart. Basically, the whole system relies on a handful of classes. In particular: -* The unique, sequential identifier associated with a given type: +* The unique sequential identifier associated with a given type: ```cpp auto index = entt::type_index::value(); @@ -483,13 +498,13 @@ Therefore, they can sometimes be even more reliable than those obtained otherwise. A type info object is an opaque class that is also copy and move constructible. -This class is returned by the `type_id` function template: +Objects of this class are returned by the `type_id` function template: ```cpp auto info = entt::type_id(); ``` -These are the information made available by this object: +These are the information made available by a `type_info` object: * The index associated with a given type: diff --git a/src/entt/core/any.hpp b/src/entt/core/any.hpp index 51ddafe25..ff5d07489 100644 --- a/src/entt/core/any.hpp +++ b/src/entt/core/any.hpp @@ -23,8 +23,10 @@ class basic_any { enum class operation : std::uint8_t { copy, move, - dtor, - comp, + assign, + transfer, + destroy, + compare, get }; @@ -65,7 +67,17 @@ class basic_any { } return (static_cast(const_cast(to))->instance = std::exchange(const_cast(from).instance, nullptr)); - case operation::dtor: + case operation::assign: + if constexpr(std::is_copy_assignable_v) { + return std::addressof(*const_cast(instance) = *static_cast(to)); + } + break; + case operation::transfer: + if constexpr(std::is_move_assignable_v) { + return std::addressof(*const_cast(instance) = std::move(*static_cast(const_cast(to)))); + } + break; + case operation::destroy: if constexpr(in_situ) { instance->~Type(); } else if constexpr(std::is_array_v) { @@ -74,9 +86,9 @@ class basic_any { delete instance; } break; - case operation::comp: + case operation::compare: if constexpr(!std::is_function_v && !std::is_array_v && is_equality_comparable_v) { - return to && (*static_cast(instance) == *static_cast(to)) ? to : nullptr; + return *static_cast(instance) == *static_cast(to) ? to : nullptr; } else { return (instance == to) ? to : nullptr; } @@ -183,7 +195,7 @@ public: /*! @brief Frees the internal storage, whatever it means. */ ~basic_any() { if(vtable && mode == policy::owner) { - vtable(operation::dtor, *this, nullptr); + vtable(operation::destroy, *this, nullptr); } } @@ -287,10 +299,40 @@ public: initialize(std::forward(args)...); } + /** + * @brief Copy assigns a value to the contained object without replacing it. + * @param other The value to assign to the contained object. + * @return True in case of success, false otherwise. + */ + bool assign(const any &other) { + if(vtable && mode != policy::cref && *info == *other.info) { + return (vtable(operation::assign, *this, other.data()) != nullptr); + } + + return false; + } + + /** + * @brief Move assigns a value to the contained object without replacing it. + * @param other The value to assign to the contained object. + * @return True in case of success, false otherwise. + */ + bool assign(any &&other) { + if(vtable && mode != policy::cref && *info == *other.info) { + if(auto *val = other.data(); val) { + return (vtable(operation::transfer, *this, val) != nullptr); + } else { + return (vtable(operation::assign, *this, std::as_const(other).data()) != nullptr); + } + } + + return false; + } + /*! @brief Destroys contained object */ void reset() { if(vtable && mode == policy::owner) { - vtable(operation::dtor, *this, nullptr); + vtable(operation::destroy, *this, nullptr); } info = &type_id(); @@ -313,7 +355,7 @@ public: */ bool operator==(const basic_any &other) const ENTT_NOEXCEPT { if(vtable && *info == *other.info) { - return (vtable(operation::comp, *this, other.data()) != nullptr); + return (vtable(operation::compare, *this, other.data()) != nullptr); } return (!vtable && !other.vtable); diff --git a/test/entt/core/any.cpp b/test/entt/core/any.cpp index ca58ef08d..1579294e8 100644 --- a/test/entt/core/any.cpp +++ b/test/entt/core/any.cpp @@ -34,7 +34,6 @@ struct not_comparable { bool operator==(const not_comparable &) const = delete; }; -template struct not_copyable { not_copyable() : payload{} {} @@ -45,7 +44,7 @@ struct not_copyable { not_copyable &operator=(const not_copyable &) = delete; not_copyable &operator=(not_copyable &&) = default; - double payload[Sz]; + double payload; }; struct alignas(64u) over_aligned {}; @@ -251,6 +250,97 @@ TEST_F(Any, SBODirectAssignment) { ASSERT_EQ(entt::any_cast(any), 42); } +TEST_F(Any, SBOAssignValue) { + entt::any any{42}; + entt::any other{3}; + entt::any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), 3); +} + +TEST_F(Any, SBOAsRefAssignValue) { + int value = 42; + entt::any any{entt::forward_as_any(value)}; + entt::any other{3}; + entt::any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), 3); + ASSERT_EQ(value, 3); +} + +TEST_F(Any, SBOAsConstRefAssignValue) { + const int value = 42; + entt::any any{entt::forward_as_any(value)}; + entt::any other{3}; + entt::any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_FALSE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(value, 42); +} + +TEST_F(Any, SBOTransferValue) { + entt::any any{42}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_TRUE(any.assign(3)); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), 3); +} + +TEST_F(Any, SBOTransferConstValue) { + const int value = 3; + entt::any any{42}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_TRUE(any.assign(entt::forward_as_any(value))); + ASSERT_EQ(entt::any_cast(any), 3); +} + +TEST_F(Any, SBOAsRefTransferValue) { + int value = 42; + entt::any any{entt::forward_as_any(value)}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_TRUE(any.assign(3)); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), 3); + ASSERT_EQ(value, 3); +} + +TEST_F(Any, SBOAsConstRefTransferValue) { + const int value = 42; + entt::any any{entt::forward_as_any(value)}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_FALSE(any.assign(3)); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(value, 42); +} + TEST_F(Any, NoSBOInPlaceTypeConstruction) { fat instance{.1, .2, .3, .4}; entt::any any{std::in_place_type, instance}; @@ -420,6 +510,112 @@ TEST_F(Any, NoSBODirectAssignment) { ASSERT_EQ(entt::any_cast(any), instance); } +TEST_F(Any, NoSBOAssignValue) { + entt::any any{fat{.1, .2, .3, .4}}; + entt::any other{fat{.0, .1, .2, .3}}; + entt::any invalid{'c'}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), (fat{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(Any, NoSBOAsRefAssignValue) { + fat instance{.1, .2, .3, .4}; + entt::any any{entt::forward_as_any(instance)}; + entt::any other{fat{.0, .1, .2, .3}}; + entt::any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), (fat{.0, .1, .2, .3})); + ASSERT_EQ(instance, (fat{.0, .1, .2, .3})); +} + +TEST_F(Any, NoSBOAsConstRefAssignValue) { + const fat instance{.1, .2, .3, .4}; + entt::any any{entt::forward_as_any(instance)}; + entt::any other{fat{.0, .1, .2, .3}}; + entt::any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_FALSE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + ASSERT_EQ(instance, (fat{.1, .2, .3, .4})); +} + +TEST_F(Any, NoSBOTransferValue) { + entt::any any{fat{.1, .2, .3, .4}}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(fat{.0, .1, .2, .3})); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), (fat{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(Any, NoSBOTransferConstValue) { + const fat instance{.0, .1, .2, .3}; + entt::any any{fat{.1, .2, .3, .4}}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(entt::forward_as_any(instance))); + ASSERT_EQ(entt::any_cast(any), (fat{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(Any, NoSBOAsRefTransferValue) { + fat instance{.1, .2, .3, .4}; + entt::any any{entt::forward_as_any(instance)}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(fat{.0, .1, .2, .3})); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), (fat{.0, .1, .2, .3})); + ASSERT_EQ(instance, (fat{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(Any, NoSBOAsConstRefTransferValue) { + const fat instance{.1, .2, .3, .4}; + entt::any any{entt::forward_as_any(instance)}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_FALSE(any.assign(fat{.0, .1, .2, .3})); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + ASSERT_EQ(instance, (fat{.1, .2, .3, .4})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + TEST_F(Any, VoidInPlaceTypeConstruction) { entt::any any{std::in_place_type}; @@ -971,13 +1167,13 @@ TEST_F(Any, AnyCast) { ASSERT_EQ(entt::any_cast(cany), 42); ASSERT_DEATH(entt::any_cast(cany), ""); - not_copyable<1> instance{}; - instance.payload[0u] = 42.; + not_copyable instance{}; + instance.payload = 42.; entt::any ref{entt::forward_as_any(instance)}; - entt::any cref{entt::forward_as_any(std::as_const(instance).payload[0u])}; + entt::any cref{entt::forward_as_any(std::as_const(instance).payload)}; - ASSERT_EQ(entt::any_cast>(std::move(ref)).payload[0u], 42.); - ASSERT_DEATH(entt::any_cast>(std::as_const(ref).as_ref()), ""); + ASSERT_EQ(entt::any_cast(std::move(ref)).payload, 42.); + ASSERT_DEATH(entt::any_cast(std::as_const(ref).as_ref()), ""); ASSERT_EQ(entt::any_cast(std::move(cref)), 42.); ASSERT_DEATH(entt::any_cast(entt::any{42}), ""); ASSERT_EQ(entt::any_cast(entt::any{42}), 42); @@ -1037,7 +1233,17 @@ TEST_F(Any, ForwardAsAny) { } TEST_F(Any, NotCopyableType) { - auto test = [](entt::any any) { + auto test = [](entt::any any, entt::any other) { + ASSERT_TRUE(any); + ASSERT_TRUE(other); + + ASSERT_TRUE(any.owner()); + ASSERT_FALSE(other.owner()); + ASSERT_EQ(any.type(), other.type()); + + ASSERT_FALSE(any.assign(other)); + ASSERT_FALSE(any.assign(std::move(other))); + entt::any copy{any}; ASSERT_TRUE(any); @@ -1055,8 +1261,8 @@ TEST_F(Any, NotCopyableType) { ASSERT_TRUE(copy.owner()); }; - test(entt::any{std::in_place_type>}); - test(entt::any{std::in_place_type>}); + const not_copyable value; + test(entt::any{std::in_place_type}, entt::forward_as_any(value)); } TEST_F(Any, Array) {