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

This commit is contained in:
Michele Caini
2021-09-03 18:23:49 +02:00
parent ef803016c0
commit 591f885e67
4 changed files with 88 additions and 47 deletions

View File

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

View File

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

View File

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

View File

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