Browse Source

any: added the ::assign function to copy/move assign the wrapped variable

Michele Caini 4 years ago
parent
commit
03e363f5d9
3 changed files with 289 additions and 26 deletions
  1. 23 8
      docs/md/core.md
  2. 50 8
      src/entt/core/any.hpp
  3. 216 10
      test/entt/core/any.cpp

+ 23 - 8
docs/md/core.md

@@ -262,11 +262,26 @@ 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/>
+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.<br/>
+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<void>()` 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.<br/>
-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<a_type>::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<a_type>();
 ```
 
-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:
 

+ 50 - 8
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<basic_any *>(const_cast<void *>(to))->instance = std::exchange(const_cast<basic_any &>(from).instance, nullptr));
-        case operation::dtor:
+        case operation::assign:
+            if constexpr(std::is_copy_assignable_v<Type>) {
+                return std::addressof(*const_cast<Type *>(instance) = *static_cast<const Type *>(to));
+            }
+            break;
+        case operation::transfer:
+            if constexpr(std::is_move_assignable_v<Type>) {
+                return std::addressof(*const_cast<Type *>(instance) = std::move(*static_cast<Type *>(const_cast<void *>(to))));
+            }
+            break;
+        case operation::destroy:
             if constexpr(in_situ<Type>) {
                 instance->~Type();
             } else if constexpr(std::is_array_v<Type>) {
@@ -74,9 +86,9 @@ class basic_any {
                 delete instance;
             }
             break;
-        case operation::comp:
+        case operation::compare:
             if constexpr(!std::is_function_v<Type> && !std::is_array_v<Type> && is_equality_comparable_v<Type>) {
-                return to && (*static_cast<const Type *>(instance) == *static_cast<const Type *>(to)) ? to : nullptr;
+                return *static_cast<const Type *>(instance) == *static_cast<const Type *>(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<Type>(std::forward<Args>(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<void>();
@@ -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);

+ 216 - 10
test/entt/core/any.cpp

@@ -34,7 +34,6 @@ struct not_comparable {
     bool operator==(const not_comparable &) const = delete;
 };
 
-template<auto Sz>
 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<int>(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<int>(any), 42);
+
+    ASSERT_TRUE(any.assign(other));
+    ASSERT_FALSE(any.assign(invalid));
+    ASSERT_EQ(entt::any_cast<int>(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<int>(any), 42);
+
+    ASSERT_TRUE(any.assign(other));
+    ASSERT_FALSE(any.assign(invalid));
+    ASSERT_EQ(entt::any_cast<int>(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<int>(any), 42);
+
+    ASSERT_FALSE(any.assign(other));
+    ASSERT_FALSE(any.assign(invalid));
+    ASSERT_EQ(entt::any_cast<int>(any), 42);
+    ASSERT_EQ(value, 42);
+}
+
+TEST_F(Any, SBOTransferValue) {
+    entt::any any{42};
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(entt::any_cast<int>(any), 42);
+
+    ASSERT_TRUE(any.assign(3));
+    ASSERT_FALSE(any.assign('c'));
+    ASSERT_EQ(entt::any_cast<int>(any), 3);
+}
+
+TEST_F(Any, SBOTransferConstValue) {
+    const int value = 3;
+    entt::any any{42};
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(entt::any_cast<int>(any), 42);
+
+    ASSERT_TRUE(any.assign(entt::forward_as_any(value)));
+    ASSERT_EQ(entt::any_cast<int>(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<int>(any), 42);
+
+    ASSERT_TRUE(any.assign(3));
+    ASSERT_FALSE(any.assign('c'));
+    ASSERT_EQ(entt::any_cast<int>(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<int>(any), 42);
+
+    ASSERT_FALSE(any.assign(3));
+    ASSERT_FALSE(any.assign('c'));
+    ASSERT_EQ(entt::any_cast<int>(any), 42);
+    ASSERT_EQ(value, 42);
+}
+
 TEST_F(Any, NoSBOInPlaceTypeConstruction) {
     fat instance{.1, .2, .3, .4};
     entt::any any{std::in_place_type<fat>, instance};
@@ -420,6 +510,112 @@ TEST_F(Any, NoSBODirectAssignment) {
     ASSERT_EQ(entt::any_cast<fat>(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<const fat &>(any), (fat{.1, .2, .3, .4}));
+
+    ASSERT_TRUE(any.assign(other));
+    ASSERT_FALSE(any.assign(invalid));
+    ASSERT_EQ(entt::any_cast<const fat &>(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<const fat &>(any), (fat{.1, .2, .3, .4}));
+
+    ASSERT_TRUE(any.assign(other));
+    ASSERT_FALSE(any.assign(invalid));
+    ASSERT_EQ(entt::any_cast<const fat &>(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<const fat &>(any), (fat{.1, .2, .3, .4}));
+
+    ASSERT_FALSE(any.assign(other));
+    ASSERT_FALSE(any.assign(invalid));
+    ASSERT_EQ(entt::any_cast<const fat &>(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<const fat &>(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<const fat &>(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<const fat &>(any), (fat{.1, .2, .3, .4}));
+
+    ASSERT_TRUE(any.assign(entt::forward_as_any(instance)));
+    ASSERT_EQ(entt::any_cast<const fat &>(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<const fat &>(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<const fat &>(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<const fat &>(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<const fat &>(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<void>};
 
@@ -971,13 +1167,13 @@ TEST_F(Any, AnyCast) {
     ASSERT_EQ(entt::any_cast<const int &>(cany), 42);
     ASSERT_DEATH(entt::any_cast<const double &>(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<not_copyable<1>>(std::move(ref)).payload[0u], 42.);
-    ASSERT_DEATH(entt::any_cast<not_copyable<1>>(std::as_const(ref).as_ref()), "");
+    ASSERT_EQ(entt::any_cast<not_copyable>(std::move(ref)).payload, 42.);
+    ASSERT_DEATH(entt::any_cast<not_copyable>(std::as_const(ref).as_ref()), "");
     ASSERT_EQ(entt::any_cast<double>(std::move(cref)), 42.);
     ASSERT_DEATH(entt::any_cast<double>(entt::any{42}), "");
     ASSERT_EQ(entt::any_cast<int>(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<not_copyable<1>>});
-    test(entt::any{std::in_place_type<not_copyable<4>>});
+    const not_copyable value;
+    test(entt::any{std::in_place_type<not_copyable>}, entt::forward_as_any(value));
 }
 
 TEST_F(Any, Array) {