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

This commit is contained in:
Michele Caini
2021-09-22 16:19:25 +02:00
parent 39211889b0
commit 03e363f5d9
3 changed files with 289 additions and 26 deletions

View File

@@ -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:

View File

@@ -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);

View File

@@ -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) {