Răsfoiți Sursa

meta: automatic conversion function to underlying type for enums (see #735)

Michele Caini 4 ani în urmă
părinte
comite
591f885e67
4 a modificat fișierele cu 88 adăugiri și 47 ștergeri
  1. 51 42
      docs/md/meta.md
  2. 3 3
      src/entt/meta/meta.hpp
  3. 1 1
      src/entt/meta/node.hpp
  4. 33 1
      test/entt/meta/meta_any.cpp

+ 51 - 42
docs/md/meta.md

@@ -13,8 +13,8 @@
   * [Container support](#container-support)
   * [Pointer-like types](#pointer-like-types)
   * [Template information](#template-information)
+  * [Automatic conversions](#automatic-conversions)
   * [Implicitly generated default constructor](#implicitly-generated-default-constructor)
-  * [Automatic arithmetic conversions](#automatic-arithmetic-conversions)
   * [Policies: the more, the less](#policies-the-more-the-less)
   * [Named constants and enums](#named-constants-and-enums)
   * [Properties and meta objects](#properties-and-meta-objects)
@@ -716,6 +716,56 @@ correspondence between real types and meta types.<br/>
 Therefore, the specialization will be used as is and the information it contains
 will be associated with the appropriate type when required.
 
+## Automatic conversions
+
+In C++, there are a number of conversions allowed between arithmetic types that
+make it convenient to work with this kind of data.<br/>
+If this were to be translated into explicit registrations with the reflection
+system, it would result in a long series of instructions such as the following:
+
+```cpp
+entt::meta<int>()
+    .conv<bool>()
+    .conv<char>()
+    // ...
+    .conv<double>();
+```
+
+Repeated for each type eligible to undergo this type of conversions. This is
+both error prone and repetitive.<br/>
+Similarly, the language allows users to silently convert unscoped enums to their
+underlying types and offers what it takes to do the same for scoped enums. It
+would result in the following if it were to be done explicitly:
+
+```cpp
+entt::meta<my_enum>()
+    .conv<std::underlying_type_t<my_enum>>();
+```
+
+Fortunately, all of this can also be avoided. `EnTT` offers implicit support for
+these types of conversions:
+
+```cpp
+entt::meta_any any{42};
+any.allow_cast<double>();
+double value = any.cast<double>();
+```
+
+With no need for registration, the conversion takes place automatically under
+the hood. The same goes for a call to `allow_cast` involving a meta type:
+
+```cpp
+entt::meta_type type = entt::resolve<int>();
+entt::meta_any any{my_enum::a_value};
+any.allow_cast(type);
+int value = any.cast<int>();
+```
+
+This should make working with arithmetic types and scoped or unscoped enums as
+easy as it is in C++.<br/>
+It's also worth noting that it's still possible to set up conversion functions
+manually and these will always be preferred over the automatic ones.
+
 ## Implicitly generated default constructor
 
 In many cases, it's useful to be able to create objects of default constructible
@@ -756,47 +806,6 @@ entt::meta_ctor ctor = entt::resolve<int>().ctor<>();
 In other terms, `ctor` is an invalid meta object unless users explicitly
 registered a meta constructor that takes no arguments for the `int` type.
 
-## Automatic arithmetic conversions
-
-In C++, there are a number of conversions allowed between arithmetic types that
-make it convenient to work with this kind of data.<br/>
-If this were to be translated into explicit registrations with the reflection
-system, it would result in a long series of instructions such as the following:
-
-```cpp
-entt::meta<int>()
-    .conv<bool>()
-    .conv<char>()
-    // ...
-    .conv<double>();
-```
-
-Repeated for each type eligible to undergo this type of conversions. This is
-both error prone and repetitive.<br/>
-Fortunately, all of this can also be avoided. `EnTT` offers implicit support for
-these types of conversions:
-
-```cpp
-entt::meta_any any{42};
-any.allow_cast<double>();
-double value = any.cast<double>();
-```
-
-With no need for registration, the conversion takes place automatically under
-the hood. The same goes for a call to `allow_cast` involving a meta type:
-
-```cpp
-entt::meta_type type = entt::resolve<double>();
-
-entt::meta_any any{42};
-any.allow_cast(type);
-double value = any.cast<double>();
-```
-
-This should make working with arithmetic types as easy as it is in C++.<br/>
-It's also worth noting that it's still possible to set up conversion functions
-manually and these will always be preferred over the automatic ones.
-
 ## Policies: the more, the less
 
 Policies are a kind of compile-time directives that can be used when registering

+ 3 - 3
src/entt/meta/meta.hpp

@@ -1039,7 +1039,7 @@ class meta_type {
             }
         }
 
-        return (type->conversion_helper && other.is_arithmetic());
+        return (type->conversion_helper && other.node->conversion_helper);
     }
 
     template<typename... Args, auto... Index>
@@ -1507,8 +1507,8 @@ bool meta_any::set(const id_type id, Type &&value) {
         return as_ref();
     } else if(const auto * const conv = internal::visit<&internal::meta_type_node::conv>([info](const auto *curr) { return curr->type->info == info; }, node); conv) {
         return conv->conv(storage.data());
-    } else if(type.is_arithmetic() && node->conversion_helper) {
-        // exploits the fact that arithmetic types are also default constructibles in all cases
+    } else if((type.is_arithmetic() || type.is_enum()) && node->conversion_helper) {
+        // exploits the fact that arithmetic types and enums are also default constructible
         auto other = type.construct();
         const double value = node->conversion_helper(storage, nullptr);
         other.node->conversion_helper(other.storage, &value);

+ 1 - 1
src/entt/meta/node.hpp

@@ -150,7 +150,7 @@ class ENTT_API meta_node {
     }
 
     [[nodiscard]] static decltype(meta_type_node::conversion_helper) meta_conversion_helper() ENTT_NOEXCEPT {
-        if constexpr(std::is_arithmetic_v<Type>) {
+        if constexpr(std::is_arithmetic_v<Type> || std::is_enum_v<Type>) {
             return +[](const any &storage, const double *value) {
                 return value ? static_cast<double>(any_cast<Type &>(const_cast<any &>(storage)) = static_cast<Type>(*value)) : static_cast<double>(any_cast<const Type &>(storage));
             };

+ 33 - 1
test/entt/meta/meta_any.cpp

@@ -46,6 +46,11 @@ struct fat_t: empty_t {
     double value[4];
 };
 
+enum class enum_class: unsigned short int {
+    foo = 0u,
+    bar = 42u
+};
+
 struct not_comparable_t {
     bool operator==(const not_comparable_t &) const = delete;
 };
@@ -852,7 +857,7 @@ TEST_F(MetaAny, ArithmeticConversion) {
 
     any = 3.1;
 
-    ASSERT_TRUE(any.allow_cast<int>());
+    ASSERT_TRUE(any.allow_cast(entt::resolve<int>()));
     ASSERT_EQ(any.type(), entt::resolve<int>());
     ASSERT_EQ(any.cast<int>(), 3);
 
@@ -867,6 +872,33 @@ TEST_F(MetaAny, ArithmeticConversion) {
     ASSERT_EQ(any.cast<char>(), 'c');
 }
 
+TEST_F(MetaAny, EnumConversion) {
+    entt::meta_any any{enum_class::foo};
+
+    ASSERT_EQ(any.type(), entt::resolve<enum_class>());
+    ASSERT_EQ(any.cast<enum_class>(), enum_class::foo);
+
+    ASSERT_TRUE(any.allow_cast<double>());
+    ASSERT_EQ(any.type(), entt::resolve<double>());
+    ASSERT_EQ(any.cast<double>(), 0.);
+
+    any = enum_class::bar;
+
+    ASSERT_TRUE(any.allow_cast(entt::resolve<int>()));
+    ASSERT_EQ(any.type(), entt::resolve<int>());
+    ASSERT_EQ(any.cast<int>(), 42);
+
+    ASSERT_TRUE(any.allow_cast<enum_class>());
+    ASSERT_EQ(any.type(), entt::resolve<enum_class>());
+    ASSERT_EQ(any.cast<enum_class>(), enum_class::bar);
+
+    any = 0;
+
+    ASSERT_TRUE(any.allow_cast(entt::resolve<enum_class>()));
+    ASSERT_EQ(any.type(), entt::resolve<enum_class>());
+    ASSERT_EQ(any.cast<enum_class>(), enum_class::foo);
+}
+
 TEST_F(MetaAny, UnmanageableType) {
     unmanageable_t instance;
     auto any = entt::forward_as_meta(instance);