Sfoglia il codice sorgente

any: added support to const references

Michele Caini 5 anni fa
parent
commit
ade3e58829
3 ha cambiato i file con 117 aggiunte e 22 eliminazioni
  1. 12 5
      docs/md/core.md
  2. 29 17
      src/entt/core/any.hpp
  3. 76 0
      test/entt/core/any.cpp

+ 12 - 5
docs/md/core.md

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

+ 29 - 17
src/entt/core/any.hpp

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

+ 76 - 0
test/entt/core/any.cpp

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