Просмотр исходного кода

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

Michele Caini 4 лет назад
Родитель
Сommit
591f885e67
4 измененных файлов с 88 добавлено и 47 удалено
  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)
   * [Container support](#container-support)
   * [Pointer-like types](#pointer-like-types)
   * [Pointer-like types](#pointer-like-types)
   * [Template information](#template-information)
   * [Template information](#template-information)
+  * [Automatic conversions](#automatic-conversions)
   * [Implicitly generated default constructor](#implicitly-generated-default-constructor)
   * [Implicitly generated default constructor](#implicitly-generated-default-constructor)
-  * [Automatic arithmetic conversions](#automatic-arithmetic-conversions)
   * [Policies: the more, the less](#policies-the-more-the-less)
   * [Policies: the more, the less](#policies-the-more-the-less)
   * [Named constants and enums](#named-constants-and-enums)
   * [Named constants and enums](#named-constants-and-enums)
   * [Properties and meta objects](#properties-and-meta-objects)
   * [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
 Therefore, the specialization will be used as is and the information it contains
 will be associated with the appropriate type when required.
 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
 ## Implicitly generated default constructor
 
 
 In many cases, it's useful to be able to create objects of default constructible
 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
 In other terms, `ctor` is an invalid meta object unless users explicitly
 registered a meta constructor that takes no arguments for the `int` type.
 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: the more, the less
 
 
 Policies are a kind of compile-time directives that can be used when registering
 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>
     template<typename... Args, auto... Index>
@@ -1507,8 +1507,8 @@ bool meta_any::set(const id_type id, Type &&value) {
         return as_ref();
         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) {
     } 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());
         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();
         auto other = type.construct();
         const double value = node->conversion_helper(storage, nullptr);
         const double value = node->conversion_helper(storage, nullptr);
         other.node->conversion_helper(other.storage, &value);
         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 {
     [[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 +[](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));
                 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];
     double value[4];
 };
 };
 
 
+enum class enum_class: unsigned short int {
+    foo = 0u,
+    bar = 42u
+};
+
 struct not_comparable_t {
 struct not_comparable_t {
     bool operator==(const not_comparable_t &) const = delete;
     bool operator==(const not_comparable_t &) const = delete;
 };
 };
@@ -852,7 +857,7 @@ TEST_F(MetaAny, ArithmeticConversion) {
 
 
     any = 3.1;
     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.type(), entt::resolve<int>());
     ASSERT_EQ(any.cast<int>(), 3);
     ASSERT_EQ(any.cast<int>(), 3);
 
 
@@ -867,6 +872,33 @@ TEST_F(MetaAny, ArithmeticConversion) {
     ASSERT_EQ(any.cast<char>(), 'c');
     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) {
 TEST_F(MetaAny, UnmanageableType) {
     unmanageable_t instance;
     unmanageable_t instance;
     auto any = entt::forward_as_meta(instance);
     auto any = entt::forward_as_meta(instance);