any: added support to const references

This commit is contained in:
Michele Caini
2020-12-14 22:37:41 +01:00
parent ee2e88476c
commit ade3e58829
3 changed files with 117 additions and 22 deletions

View File

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

View File

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

View File

@@ -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{};