any: added support to const references
This commit is contained in:
@@ -254,16 +254,17 @@ When in doubt about the type of object contained, the `type` member function of
|
||||
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:
|
||||
an opaque container for const and non-const references:
|
||||
|
||||
```cpp
|
||||
int value;
|
||||
entt::any any{std::ref(value)};
|
||||
entt::any cany{std::cref(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
|
||||
pointer to the original instance rather than making a copy of it or moving it
|
||||
internally. The _contained_ object is never destroyed and users must ensure that
|
||||
its lifetime exceeds that of the container.<br/>
|
||||
Similarly, it's possible to create non-owning copies of `any` from an existing
|
||||
object:
|
||||
@@ -277,14 +278,20 @@ In this case, it doesn't matter if the original 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.<br/>
|
||||
It means that, starting from the example above, both `ref` and` other` will
|
||||
This 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.
|
||||
|
||||
As a side note, it's worth mentioning that, while everything works transparently
|
||||
when it comes to non-const references, there are some exceptions when it comes
|
||||
to const references.<br/>
|
||||
In particular, the `data` member function invoked on a non-const instance of
|
||||
`any` that wraps a const reference will return a null pointer in all cases.
|
||||
|
||||
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.<br/>
|
||||
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
|
||||
but will only trigger an assert in debug mode, otherwise resulting in undefined
|
||||
behavior in case of misuse in release mode.
|
||||
|
||||
# Type support
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
#include "type_info.hpp"
|
||||
#include "type_traits.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -15,7 +16,7 @@ namespace entt {
|
||||
|
||||
/*! @brief A SBO friendly, type-safe container for single values of any type. */
|
||||
class any {
|
||||
enum class operation { COPY, MOVE, DTOR, ADDR, REF, TYPE };
|
||||
enum class operation { COPY, MOVE, DTOR, ADDR, CADDR, REF, TYPE };
|
||||
|
||||
using storage_type = std::aligned_storage_t<sizeof(double[2]), alignof(double[2])>;
|
||||
using vtable_type = const void *(const operation, const any &, const void *);
|
||||
@@ -47,6 +48,8 @@ class any {
|
||||
case operation::DTOR:
|
||||
break;
|
||||
case operation::ADDR:
|
||||
return std::is_const_v<std::remove_reference_t<Type>> ? nullptr : from.instance;
|
||||
case operation::CADDR:
|
||||
return from.instance;
|
||||
case operation::TYPE:
|
||||
as_type_info(to) = type_id<std::remove_reference_t<Type>>();
|
||||
@@ -66,6 +69,7 @@ class any {
|
||||
instance->~Type();
|
||||
break;
|
||||
case operation::ADDR:
|
||||
case operation::CADDR:
|
||||
return instance;
|
||||
case operation::REF:
|
||||
as_any(to).vtable = basic_vtable<std::add_lvalue_reference_t<Type>>;
|
||||
@@ -87,6 +91,7 @@ class any {
|
||||
delete static_cast<const Type *>(from.instance);
|
||||
break;
|
||||
case operation::ADDR:
|
||||
case operation::CADDR:
|
||||
return from.instance;
|
||||
case operation::REF:
|
||||
as_any(to).vtable = basic_vtable<std::add_lvalue_reference_t<Type>>;
|
||||
@@ -201,12 +206,12 @@ public:
|
||||
* @return An opaque pointer the contained instance, if any.
|
||||
*/
|
||||
[[nodiscard]] const void * data() const ENTT_NOEXCEPT {
|
||||
return vtable(operation::ADDR, *this, nullptr);
|
||||
return vtable(operation::CADDR, *this, nullptr);
|
||||
}
|
||||
|
||||
/*! @copydoc data */
|
||||
[[nodiscard]] void * data() ENTT_NOEXCEPT {
|
||||
return const_cast<void *>(std::as_const(*this).data());
|
||||
return const_cast<void *>(vtable(operation::ADDR, *this, nullptr));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,7 +230,7 @@ public:
|
||||
* @return False if the wrapper is empty, true otherwise.
|
||||
*/
|
||||
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
|
||||
return !(vtable(operation::ADDR, *this, nullptr) == nullptr);
|
||||
return !(vtable(operation::CADDR, *this, nullptr) == nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,12 +265,12 @@ private:
|
||||
|
||||
/**
|
||||
* @brief Performs type-safe access to the contained object.
|
||||
* @param any Target any object.
|
||||
* @param data Target any object.
|
||||
* @return The element converted to the requested type.
|
||||
*/
|
||||
template<typename Type>
|
||||
Type any_cast(const any &any) ENTT_NOEXCEPT {
|
||||
auto *instance = any_cast<std::remove_cv_t<std::remove_reference_t<Type>>>(&any);
|
||||
Type any_cast(const any &data) ENTT_NOEXCEPT {
|
||||
auto * const instance = any_cast<std::remove_cv_t<std::remove_reference_t<Type>>>(&data);
|
||||
ENTT_ASSERT(instance);
|
||||
return static_cast<Type>(*instance);
|
||||
}
|
||||
@@ -273,17 +278,24 @@ Type any_cast(const any &any) ENTT_NOEXCEPT {
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type>
|
||||
Type any_cast(any &any) ENTT_NOEXCEPT {
|
||||
auto *instance = any_cast<std::remove_cv_t<std::remove_reference_t<Type>>>(&any);
|
||||
ENTT_ASSERT(instance);
|
||||
return static_cast<Type>(*instance);
|
||||
Type any_cast(any &data) ENTT_NOEXCEPT {
|
||||
if constexpr(!std::is_reference_v<Type> || std::is_const_v<std::remove_reference_t<Type>>) {
|
||||
// last attempt to make wrappers for const references return their values
|
||||
auto * const instance = any_cast<std::remove_cv_t<std::remove_reference_t<Type>>>(&std::as_const(data));
|
||||
ENTT_ASSERT(instance);
|
||||
return static_cast<Type>(*instance);
|
||||
} else {
|
||||
auto * const instance = any_cast<std::remove_cv_t<std::remove_reference_t<Type>>>(&data);
|
||||
ENTT_ASSERT(instance);
|
||||
return static_cast<Type>(*instance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type>
|
||||
Type any_cast(any &&any) ENTT_NOEXCEPT {
|
||||
auto *instance = any_cast<std::remove_cv_t<std::remove_reference_t<Type>>>(&any);
|
||||
Type any_cast(any &&data) ENTT_NOEXCEPT {
|
||||
auto * const instance = any_cast<std::remove_cv_t<std::remove_reference_t<Type>>>(&data);
|
||||
ENTT_ASSERT(instance);
|
||||
return static_cast<Type>(std::move(*instance));
|
||||
}
|
||||
@@ -291,15 +303,15 @@ Type any_cast(any &&any) ENTT_NOEXCEPT {
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type>
|
||||
const Type * any_cast(const any *any) ENTT_NOEXCEPT {
|
||||
return (any->type() == type_id<Type>() ? static_cast<const Type *>(any->data()) : nullptr);
|
||||
const Type * any_cast(const any *data) ENTT_NOEXCEPT {
|
||||
return (data->type() == type_id<Type>() ? static_cast<const Type *>(data->data()) : nullptr);
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type>
|
||||
Type * any_cast(any *any) ENTT_NOEXCEPT {
|
||||
return (any->type() == type_id<Type>() ? static_cast<Type *>(any->data()) : nullptr);
|
||||
Type * any_cast(any *data) ENTT_NOEXCEPT {
|
||||
return (data->type() == type_id<Type>() ? static_cast<Type *>(data->data()) : nullptr);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -80,6 +80,27 @@ TEST(Any, SBOAsRefConstruction) {
|
||||
ASSERT_EQ(other.data(), any.data());
|
||||
}
|
||||
|
||||
TEST(Any, SBOAsConstRefConstruction) {
|
||||
int value = 42;
|
||||
entt::any any{std::cref(value)};
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::type_id<int>());
|
||||
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
|
||||
ASSERT_EQ(entt::any_cast<int>(&any), nullptr);
|
||||
ASSERT_EQ(entt::any_cast<const int &>(any), 42);
|
||||
ASSERT_EQ(entt::any_cast<int>(any), 42);
|
||||
ASSERT_EQ(any.data(), nullptr);
|
||||
ASSERT_EQ(std::as_const(any).data(), &value);
|
||||
|
||||
auto other = as_ref(any);
|
||||
|
||||
ASSERT_TRUE(other);
|
||||
ASSERT_EQ(other.type(), entt::type_id<int>());
|
||||
ASSERT_EQ(entt::any_cast<int>(other), 42);
|
||||
ASSERT_EQ(other.data(), any.data());
|
||||
}
|
||||
|
||||
TEST(Any, SBOCopyConstruction) {
|
||||
entt::any any{42};
|
||||
entt::any other{any};
|
||||
@@ -177,6 +198,27 @@ TEST(Any, NoSBOAsRefConstruction) {
|
||||
ASSERT_EQ(other.data(), any.data());
|
||||
}
|
||||
|
||||
TEST(Any, NoSBOAsConstRefConstruction) {
|
||||
fat instance{{.1, .2, .3, .4}};
|
||||
entt::any any{std::cref(instance)};
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::type_id<fat>());
|
||||
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
|
||||
ASSERT_EQ(entt::any_cast<fat>(&any), nullptr);
|
||||
ASSERT_EQ(entt::any_cast<const fat &>(any), instance);
|
||||
ASSERT_EQ(entt::any_cast<fat>(any), instance);
|
||||
ASSERT_EQ(any.data(), nullptr);
|
||||
ASSERT_EQ(std::as_const(any).data(), &instance);
|
||||
|
||||
auto other = as_ref(any);
|
||||
|
||||
ASSERT_TRUE(other);
|
||||
ASSERT_EQ(other.type(), entt::type_id<fat>());
|
||||
ASSERT_EQ(entt::any_cast<fat>(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};
|
||||
@@ -440,6 +482,23 @@ TEST(Any, SBOWithRefSwap) {
|
||||
ASSERT_EQ(rhs.data(), &value);
|
||||
}
|
||||
|
||||
TEST(Any, SBOWithConstRefSwap) {
|
||||
int value = 3;
|
||||
entt::any lhs{std::cref(value)};
|
||||
entt::any rhs{'c'};
|
||||
|
||||
std::swap(lhs, rhs);
|
||||
|
||||
ASSERT_EQ(lhs.type(), entt::type_id<char>());
|
||||
ASSERT_EQ(rhs.type(), entt::type_id<int>());
|
||||
ASSERT_EQ(entt::any_cast<int>(&lhs), nullptr);
|
||||
ASSERT_EQ(entt::any_cast<char>(&rhs), nullptr);
|
||||
ASSERT_EQ(entt::any_cast<char>(lhs), 'c');
|
||||
ASSERT_EQ(entt::any_cast<int>(rhs), 3);
|
||||
ASSERT_EQ(rhs.data(), nullptr);
|
||||
ASSERT_EQ(std::as_const(rhs).data(), &value);
|
||||
}
|
||||
|
||||
TEST(Any, SBOWithEmptySwap) {
|
||||
entt::any lhs{'c'};
|
||||
entt::any rhs{};
|
||||
@@ -498,6 +557,23 @@ TEST(Any, NoSBOWithRefSwap) {
|
||||
ASSERT_EQ(rhs.data(), &value);
|
||||
}
|
||||
|
||||
TEST(Any, NoSBOWithConstRefSwap) {
|
||||
int value = 3;
|
||||
entt::any lhs{std::cref(value)};
|
||||
entt::any rhs{fat{{.1, .2, .3, .4}}};
|
||||
|
||||
std::swap(lhs, rhs);
|
||||
|
||||
ASSERT_EQ(lhs.type(), entt::type_id<fat>());
|
||||
ASSERT_EQ(rhs.type(), entt::type_id<int>());
|
||||
ASSERT_EQ(entt::any_cast<int>(&lhs), nullptr);
|
||||
ASSERT_EQ(entt::any_cast<fat>(&rhs), nullptr);
|
||||
ASSERT_EQ(entt::any_cast<fat>(lhs), (fat{{.1, .2, .3, .4}}));
|
||||
ASSERT_EQ(entt::any_cast<int>(rhs), 3);
|
||||
ASSERT_EQ(rhs.data(), nullptr);
|
||||
ASSERT_EQ(std::as_const(rhs).data(), &value);
|
||||
}
|
||||
|
||||
TEST(Any, NoSBOWithEmptySwap) {
|
||||
entt::any lhs{fat{{.1, .2, .3, .4}}};
|
||||
entt::any rhs{};
|
||||
|
||||
Reference in New Issue
Block a user