Kaynağa Gözat

any: comparison functions (==, !=)

Michele Caini 5 yıl önce
ebeveyn
işleme
2daaf899d3
5 değiştirilmiş dosya ile 106 ekleme ve 16 silme
  1. 0 1
      TODO
  2. 11 1
      docs/md/core.md
  3. 3 12
      docs/md/meta.md
  4. 37 2
      src/entt/core/any.hpp
  5. 55 0
      test/entt/core/any.cpp

+ 0 - 1
TODO

@@ -24,7 +24,6 @@ WIP:
 * HP: pass the registry to pools, basic poly storage should have only component member
 * HP: make view pack work also with groups, make packs input iterator only, add view adapter for external sources
 * HP: write documentation for custom storages and views!!
-* HP: add support for const references to any/poly (actual copy on copy, data vs cdata)
 * HP: any/poly: configurable sbo size, compile-time policies like sbo-required.
 * HP: registry: use a poly object for pools, no more pool_data type.
 * HP: make runtime views use opaque storage and therefore return also elements.

+ 11 - 1
docs/md/core.md

@@ -251,7 +251,17 @@ 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/>
 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.
+invalid `type_info` object if the container is empty. The type is also used
+internally when comparing two `any` objects:
+
+```cpp
+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
+`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
 an opaque container for const and non-const references:

+ 3 - 12
docs/md/meta.md

@@ -202,18 +202,9 @@ integrate it with the meta type system without having to duplicate the code.
 The API is very similar to that of the `any` type. The class `meta_any` _wraps_
 many of the feature to infer a meta node, before forwarding some or all of the
 arguments to the underlying storage.<br/>
-Among the few relevant differences, instances of `meta_any` are comparable,
-while those of `any` are not:
-
-```cpp
-entt::meta_any any{42};
-entt::meta_any other{'c'};
-
-const bool equal = (any == other);
-```
-
-Also, `meta_any` adds support for containers and pointer-like types (see the
-following sections for more details).<br/>
+Among the few relevant differences, `meta_any` adds support for containers and
+pointer-like types (see the following sections for more details), while `any`
+does not.<br/>
 Similar to `any`, this class can also be used to create _aliases_ for unmanaged
 objects either upon construction using `std::ref` and `std::cref` or from an
 existing instance by means of the `as_ref` function. However, unlike `any`,

+ 37 - 2
src/entt/core/any.hpp

@@ -16,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, CADDR, REF, CREF, TYPE };
+    enum class operation { COPY, MOVE, DTOR, COMP, ADDR, CADDR, REF, CREF, 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 *);
@@ -24,6 +24,15 @@ class any {
     template<typename Type>
     static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
 
+    template<typename Type>
+    [[nodiscard]] static bool compare(const void *lhs, const void *rhs) {
+        if constexpr(!std::is_function_v<Type> && is_equality_comparable_v<Type>) {
+            return *static_cast<const Type *>(lhs) == *static_cast<const Type *>(rhs);
+        } else {
+            return lhs == rhs;
+        }
+    }
+
     static type_info & as_type_info(const void *data) {
         return *const_cast<type_info *>(static_cast<const type_info *>(data));
     }
@@ -50,6 +59,8 @@ class any {
                 [[fallthrough]];
             case operation::DTOR:
                 break;
+            case operation::COMP:
+                return compare<base_type>(from.instance, to) ? to : nullptr;
             case operation::ADDR:
                 return std::is_const_v<base_type> ? nullptr : from.instance;
             case operation::CADDR:
@@ -71,6 +82,8 @@ class any {
             case operation::DTOR:
                 instance->~Type();
                 break;
+            case operation::COMP:
+                return compare<Type>(instance, to) ? to : nullptr;
             case operation::ADDR:
             case operation::CADDR:
                 return instance;
@@ -98,6 +111,8 @@ class any {
             case operation::DTOR:
                 delete static_cast<const Type *>(from.instance);
                 break;
+            case operation::COMP:
+                return compare<Type>(from.instance, to) ? to : nullptr;
             case operation::ADDR:
             case operation::CADDR:
                 return from.instance;
@@ -188,7 +203,7 @@ public:
     /**
      * @brief Assignment operator.
      * @param other The instance to assign from.
-     * @return This any any object.
+     * @return This any object.
      */
     any & operator=(any other) {
         swap(*this, other);
@@ -237,6 +252,15 @@ public:
         return !(vtable(operation::CADDR, *this, nullptr) == nullptr);
     }
 
+    /**
+     * @brief Checks if two wrappers differ in their content.
+     * @param other Wrapper with which to compare.
+     * @return False if the two objects differ in their content, true otherwise.
+     */
+    bool operator==(const any &other) const ENTT_NOEXCEPT {
+        return type() == other.type() && (vtable(operation::COMP, *this, other.data()) == other.data());
+    }
+
     /**
      * @brief Swaps two any objects.
      * @param lhs A valid any object.
@@ -274,6 +298,17 @@ private:
 };
 
 
+/**
+ * @brief Checks if two wrappers differ in their content.
+ * @param lhs A wrapper, either empty or not.
+ * @param rhs A wrapper, either empty or not.
+ * @return True if the two wrappers differ in their content, false otherwise.
+ */
+[[nodiscard]] inline bool operator!=(const any &lhs, const any &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+
 /**
  * @brief Performs type-safe access to the contained object.
  * @param data Target any object.

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

@@ -18,6 +18,10 @@ struct empty {
     ~empty() { ++counter; }
 };
 
+struct not_comparable {
+    bool operator==(const not_comparable &) const = delete;
+};
+
 TEST(Any, SBO) {
     entt::any any{'c'};
 
@@ -712,6 +716,57 @@ TEST(Any, AsRef) {
     ASSERT_NE(entt::any_cast<int>(&cref), any.data());
 }
 
+TEST(Any, Comparable) {
+    auto test = [](auto value, auto other) {
+        entt::any any{value};
+
+        ASSERT_EQ(any, any);
+        ASSERT_EQ(any, entt::any{value});
+        ASSERT_NE(entt::any{other}, any);
+        ASSERT_NE(any, entt::any{});
+
+        ASSERT_TRUE(any == any);
+        ASSERT_TRUE(any == entt::any{value});
+        ASSERT_FALSE(entt::any{other} == any);
+        ASSERT_TRUE(any != entt::any{other});
+        ASSERT_TRUE(entt::any{} != any);
+    };
+
+    int value = 42;
+
+    test('c', 'a');
+    test(fat{{.1, .2, .3, .4}}, fat{{.0, .1, .2, .3}});
+    test(std::ref(value), 3);
+    test(3, std::cref(value));
+}
+
+TEST(Any, NotComparable) {
+    entt::any any{not_comparable{}};
+
+    ASSERT_EQ(any, any);
+    ASSERT_NE(any, entt::any{not_comparable{}});
+    ASSERT_NE(entt::any{}, any);
+
+    ASSERT_TRUE(any == any);
+    ASSERT_FALSE(any == entt::any{not_comparable{}});
+    ASSERT_TRUE(entt::any{} != any);
+}
+
+TEST(Any, CompareVoid) {
+    entt::any any{std::in_place_type<void>};
+
+    ASSERT_EQ(any, any);
+    ASSERT_EQ(any, entt::any{std::in_place_type<void>});
+    ASSERT_NE(entt::any{'a'}, any);
+    ASSERT_EQ(any, entt::any{});
+
+    ASSERT_TRUE(any == any);
+    ASSERT_TRUE(any == entt::any{std::in_place_type<void>});
+    ASSERT_FALSE(entt::any{'a'} == any);
+    ASSERT_TRUE(any != entt::any{'a'});
+    ASSERT_FALSE(entt::any{} != any);
+}
+
 TEST(Any, AnyCast) {
     entt::any any{42};
     const auto &cany = any;