Browse Source

meta: automatic arithmetic conversions (see #735)

Michele Caini 4 years ago
parent
commit
bac51045e5

+ 42 - 0
docs/md/meta.md

@@ -14,6 +14,7 @@
   * [Pointer-like types](#pointer-like-types)
   * [Template information](#template-information)
   * [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)
@@ -755,6 +756,47 @@ 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

+ 18 - 12
src/entt/meta/meta.hpp

@@ -1022,30 +1022,30 @@ private:
 
 /*! @brief Opaque wrapper for types. */
 class meta_type {
-    [[nodiscard]] static bool can_cast_or_convert(const internal::meta_type_node *type, const type_info info) ENTT_NOEXCEPT {
-        if(type->info == info) {
+    [[nodiscard]] static bool can_cast_or_convert(const internal::meta_type_node *type, const meta_type other) ENTT_NOEXCEPT {
+        if(type->info == other.info()) {
             return true;
         }
 
         for(const auto *curr = type->conv; curr; curr = curr->next) {
-            if(curr->type->info == info) {
+            if(curr->type->info == other.info()) {
                 return true;
             }
         }
 
         for(const auto *curr = type->base; curr; curr = curr->next) {
-            if(can_cast_or_convert(curr->type, info)) {
+            if(can_cast_or_convert(curr->type, other)) {
                 return true;
             }
         }
 
-        return false;
+        return (type->conversion_helper && other.is_arithmetic());
     }
 
     template<typename... Args, auto... Index>
     [[nodiscard]] static const internal::meta_ctor_node * ctor(const internal::meta_ctor_node *curr, std::index_sequence<Index...>) {
         for(; curr; curr = curr->next) {
-            if(curr->arity == sizeof...(Args) && (can_cast_or_convert(internal::meta_info<Args>::resolve(), curr->arg(Index).info()) && ...)) {
+            if(curr->arity == sizeof...(Args) && (can_cast_or_convert(internal::meta_info<Args>::resolve(), curr->arg(Index)) && ...)) {
                 return curr;
             }
         }
@@ -1343,8 +1343,8 @@ public:
 
             for(size_type next{}; next < sz && next == (direct + ext); ++next) {
                 const auto type = args[next].type();
-                const auto req = it->arg(next).info();
-                type.info() == req ? ++direct : (ext += can_cast_or_convert(type.node, req));
+                const auto other = it->arg(next);
+                type.info() == other.info() ? ++direct : (ext += can_cast_or_convert(type.node, other));
             }
 
             if((direct + ext) == sz) {
@@ -1507,6 +1507,12 @@ 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
+        auto other = type.construct();
+        const double value = node->conversion_helper(storage, nullptr);
+        other.node->conversion_helper(other.storage, &value);
+        return other;
     }
 
     return {};
@@ -1514,12 +1520,12 @@ bool meta_any::set(const id_type id, Type &&value) {
 
 
 inline bool meta_any::allow_cast(const meta_type &type) {
-    if(!node) { return false; }
+    if(auto any = std::as_const(*this).allow_cast(type); any) {
+        if(any.storage.owner()) {
+            *this = std::move(any);
+        }
 
-    if(const auto info = type.info(); node->info == info || internal::visit<&internal::meta_type_node::base>([info](const auto *curr) { return curr->type->info == info; }, node)) {
         return true;
-    } 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 static_cast<bool>(*this = conv->conv(std::as_const(storage).data()));
     }
 
     return false;

+ 12 - 0
src/entt/meta/node.hpp

@@ -122,6 +122,7 @@ struct meta_type_node {
     const size_type size_of;
     meta_traits traits;
     meta_any(* const default_constructor)();
+    double(* const conversion_helper)(const any &, const double *);
     const meta_template_node *const templ;
     meta_ctor_node *ctor{nullptr};
     meta_base_node *base{nullptr};
@@ -148,6 +149,16 @@ 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>) {
+            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));
+            };
+        } else {
+            return nullptr;
+        }
+    }
+
     [[nodiscard]] static meta_template_node * meta_template_info() ENTT_NOEXCEPT {
         if constexpr(is_complete_v<meta_template_traits<Type>>) {
             static meta_template_node node{
@@ -182,6 +193,7 @@ public:
                 | (is_complete_v<meta_sequence_container_traits<Type>> ? internal::meta_traits::IS_META_SEQUENCE_CONTAINER : internal::meta_traits::IS_NONE)
                 | (is_complete_v<meta_associative_container_traits<Type>> ? internal::meta_traits::IS_META_ASSOCIATIVE_CONTAINER : internal::meta_traits::IS_NONE),
             meta_default_constructor(),
+            meta_conversion_helper(),
             meta_template_info()
         };
 

+ 2 - 2
test/entt/entity/view.cpp

@@ -141,7 +141,7 @@ TEST(SingleComponentView, ElementAccess) {
     }
 
     ASSERT_EQ(view[e0], 42);
-    ASSERT_EQ(view[e1], 3);
+    ASSERT_EQ(cview[e1], 3);
 }
 
 TEST(SingleComponentView, Contains) {
@@ -618,7 +618,7 @@ TEST(MultiComponentView, ElementAccess) {
     registry.emplace<char>(e1, '1');
 
     ASSERT_EQ(view[e0], std::make_tuple(42, '0'));
-    ASSERT_EQ(view[e1], std::make_tuple(3, '1'));
+    ASSERT_EQ(cview[e1], std::make_tuple(3, '1'));
 }
 
 TEST(MultiComponentView, Contains) {

+ 39 - 20
test/entt/meta/meta_any.cpp

@@ -1,4 +1,5 @@
 #include <algorithm>
+#include <type_traits>
 #include <gtest/gtest.h>
 #include <entt/core/hashed_string.hpp>
 #include <entt/meta/factory.hpp>
@@ -10,6 +11,7 @@ struct clazz_t {
 
     void member(int i) { value = i; }
     static void func() { c = 'd'; }
+    operator int() const { return value; }
 
     static inline char c = 'c';
     int value;
@@ -61,8 +63,7 @@ struct MetaAny: ::testing::Test {
         using namespace entt::literals;
 
         entt::meta<double>()
-            .type("double"_hs)
-            .conv<int>();
+            .type("double"_hs);
 
         entt::meta<empty_t>()
             .type("empty"_hs)
@@ -77,7 +78,8 @@ struct MetaAny: ::testing::Test {
             .type("clazz"_hs)
             .data<&clazz_t::value>("value"_hs)
             .func<&clazz_t::member>("member"_hs)
-            .func<clazz_t::func>("func"_hs);
+            .func<clazz_t::func>("func"_hs)
+            .conv<int>();
 
         empty_t::destroy_counter = 0;
         empty_t::destructor_counter = 0;
@@ -817,35 +819,52 @@ TEST_F(MetaAny, Cast) {
 }
 
 TEST_F(MetaAny, Convert) {
-    entt::meta_any any{42.};
+    entt::meta_any any{clazz_t{}};
+    any.cast<clazz_t &>().value = 42;
+    auto as_int = std::as_const(any).allow_cast<int>();
 
     ASSERT_TRUE(any);
-    ASSERT_EQ(any.type(), entt::resolve<double>());
-    ASSERT_TRUE(any.allow_cast<double>());
-    ASSERT_FALSE(any.allow_cast<char>());
-    ASSERT_EQ(any.type(), entt::resolve<double>());
-    ASSERT_EQ(any.cast<double>(), 42.);
+    ASSERT_EQ(any.type(), entt::resolve<clazz_t>());
+    ASSERT_TRUE(any.allow_cast<clazz_t>());
+    ASSERT_EQ(any.type(), entt::resolve<clazz_t>());
     ASSERT_TRUE(any.allow_cast<int>());
     ASSERT_EQ(any.type(), entt::resolve<int>());
     ASSERT_EQ(any.cast<int>(), 42);
+
+    ASSERT_TRUE(as_int);
+    ASSERT_EQ(as_int.type(), entt::resolve<int>());
+    ASSERT_EQ(as_int.cast<int>(), 42);
+
+    ASSERT_TRUE(as_int.allow_cast<char>());
+    ASSERT_EQ(as_int.type(), entt::resolve<char>());
+    ASSERT_EQ(as_int.cast<char>(), char{42});
 }
 
-TEST_F(MetaAny, ConstConvert) {
-    const entt::meta_any any{42.};
+TEST_F(MetaAny, ArithmeticConversion) {
+    entt::meta_any any{'c'};
+
+    ASSERT_EQ(any.type(), entt::resolve<char>());
+    ASSERT_EQ(any.cast<char>(), 'c');
 
-    ASSERT_TRUE(any);
-    ASSERT_EQ(any.type(), entt::resolve<double>());
     ASSERT_TRUE(any.allow_cast<double>());
-    ASSERT_FALSE(any.allow_cast<char>());
     ASSERT_EQ(any.type(), entt::resolve<double>());
-    ASSERT_EQ(any.cast<double>(), 42.);
+    ASSERT_EQ(any.cast<double>(), static_cast<double>('c'));
 
-    auto other = any.allow_cast<int>();
+    any = 3.1;
 
-    ASSERT_EQ(any.type(), entt::resolve<double>());
-    ASSERT_EQ(any.cast<double>(), 42.);
-    ASSERT_EQ(other.type(), entt::resolve<int>());
-    ASSERT_EQ(other.cast<int>(), 42);
+    ASSERT_TRUE(any.allow_cast<int>());
+    ASSERT_EQ(any.type(), entt::resolve<int>());
+    ASSERT_EQ(any.cast<int>(), 3);
+
+    ASSERT_TRUE(any.allow_cast<float>());
+    ASSERT_EQ(any.type(), entt::resolve<float>());
+    ASSERT_EQ(any.cast<float>(), 3.f);
+
+    any = static_cast<float>('c');
+
+    ASSERT_TRUE(any.allow_cast<char>());
+    ASSERT_EQ(any.type(), entt::resolve<char>());
+    ASSERT_EQ(any.cast<char>(), 'c');
 }
 
 TEST_F(MetaAny, UnmanageableType) {

+ 20 - 20
test/entt/meta/meta_container.cpp

@@ -5,17 +5,17 @@
 #include <entt/meta/meta.hpp>
 #include <entt/meta/resolve.hpp>
 
+struct invalid_type {};
+
 struct MetaContainer: ::testing::Test {
     void SetUp() override {
         using namespace entt::literals;
 
         entt::meta<double>()
-            .type("double"_hs)
-            .conv<int>();
+            .type("double"_hs);
 
         entt::meta<int>()
-            .type("int"_hs)
-            .conv<char>();
+            .type("int"_hs);
     }
 
     void TearDown() override {
@@ -132,7 +132,7 @@ TEST_F(MetaContainer, StdVector) {
     auto ret = view.insert(it, 0);
 
     ASSERT_TRUE(ret);
-    ASSERT_FALSE(view.insert(ret, 'c'));
+    ASSERT_FALSE(view.insert(ret, invalid_type{}));
     ASSERT_TRUE(view.insert(++ret, 1.));
 
     ASSERT_EQ(view.size(), 5u);
@@ -175,7 +175,7 @@ TEST_F(MetaContainer, StdArray) {
 
     ASSERT_FALSE(ret);
     ASSERT_FALSE(view.insert(it, 'c'));
-    ASSERT_FALSE(view.insert(++it, 1));
+    ASSERT_FALSE(view.insert(++it, 1.));
 
     ASSERT_EQ(view.size(), 3u);
     ASSERT_EQ((*view.begin()).cast<int>(), 2);
@@ -209,8 +209,8 @@ TEST_F(MetaContainer, StdMap) {
 
     ASSERT_EQ((*view.find(3)).second.cast<char>(), 'd');
 
-    ASSERT_FALSE(view.insert('a', 'a'));
-    ASSERT_FALSE(view.insert(1, 1.));
+    ASSERT_FALSE(view.insert(invalid_type{}, 'a'));
+    ASSERT_FALSE(view.insert(1, invalid_type{}));
 
     ASSERT_TRUE(view.insert(0, 'a'));
     ASSERT_TRUE(view.insert(1., static_cast<int>('b')));
@@ -219,17 +219,17 @@ TEST_F(MetaContainer, StdMap) {
     ASSERT_EQ((*view.find(0)).second.cast<char>(), 'a');
     ASSERT_EQ((*view.find(1.)).second.cast<char>(), 'b');
 
-    ASSERT_FALSE(view.erase('c'));
+    ASSERT_FALSE(view.erase(invalid_type{}));
+    ASSERT_FALSE(view.find(invalid_type{}));
     ASSERT_EQ(view.size(), 5u);
-    ASSERT_FALSE(view.find('c'));
 
     ASSERT_TRUE(view.erase(0));
     ASSERT_EQ(view.size(), 4u);
     ASSERT_EQ(view.find(0), view.end());
 
-    (*view.find(1)).second.cast<char &>() = 'f';
+    (*view.find(1.)).second.cast<char &>() = 'f';
 
-    ASSERT_EQ((*view.find(1)).second.cast<char>(), 'f');
+    ASSERT_EQ((*view.find(1.f)).second.cast<char>(), 'f');
 
     ASSERT_TRUE(view.erase(1.));
     ASSERT_TRUE(view.clear());
@@ -253,26 +253,26 @@ TEST_F(MetaContainer, StdSet) {
 
     ASSERT_EQ((*view.find(3)).first.cast<int>(), 3);
 
-    ASSERT_FALSE(view.insert('0'));
+    ASSERT_FALSE(view.insert(invalid_type{}));
 
-    ASSERT_TRUE(view.insert(0));
+    ASSERT_TRUE(view.insert(.0));
     ASSERT_TRUE(view.insert(1));
 
     ASSERT_EQ(view.size(), 5u);
     ASSERT_EQ((*view.find(0)).first.cast<int>(), 0);
     ASSERT_EQ((*view.find(1.)).first.cast<int>(), 1);
 
-    ASSERT_FALSE(view.erase('c'));
+    ASSERT_FALSE(view.erase(invalid_type{}));
+    ASSERT_FALSE(view.find(invalid_type{}));
     ASSERT_EQ(view.size(), 5u);
-    ASSERT_FALSE(view.find('c'));
 
     ASSERT_TRUE(view.erase(0));
     ASSERT_EQ(view.size(), 4u);
     ASSERT_EQ(view.find(0), view.end());
 
-    ASSERT_EQ((*view.find(1)).first.try_cast<int>(), nullptr);
-    ASSERT_NE((*view.find(1)).first.try_cast<const int>(), nullptr);
-    ASSERT_EQ((*view.find(1)).first.cast<const int &>(), 1);
+    ASSERT_EQ((*view.find(1.f)).first.try_cast<int>(), nullptr);
+    ASSERT_NE((*view.find(1.)).first.try_cast<const int>(), nullptr);
+    ASSERT_EQ((*view.find(true)).first.cast<const int &>(), 1);
 
     ASSERT_TRUE(view.erase(1.));
     ASSERT_TRUE(view.clear());
@@ -478,7 +478,7 @@ TEST_F(MetaContainer, StdVectorBool) {
     auto ret = view.insert(it, true);
 
     ASSERT_TRUE(ret);
-    ASSERT_FALSE(view.insert(ret, 'c'));
+    ASSERT_FALSE(view.insert(ret, invalid_type{}));
     ASSERT_TRUE(view.insert(++ret, false));
 
     ASSERT_EQ(view.size(), 5u);

+ 34 - 10
test/entt/meta/meta_ctor.cpp

@@ -6,8 +6,14 @@
 #include <entt/meta/meta.hpp>
 #include <entt/meta/resolve.hpp>
 
-struct base_t { base_t(): value{'c'} {} char value; };
-struct derived_t: base_t { derived_t(): base_t{} {} };
+struct base_t {
+    base_t(): value{'c'} {}
+    char value;
+};
+
+struct derived_t: base_t {
+    derived_t(): base_t{} {}
+};
 
 struct clazz_t {
     clazz_t(const base_t &other, int iv)
@@ -18,12 +24,14 @@ struct clazz_t {
         : i{iv}, c{cv}
     {}
 
+    operator int() const { return i; }
+
     static clazz_t factory(int value) {
-        return {value, 'c'};
+        return { value, 'c' };
     }
 
     static clazz_t factory(base_t other, int value, int mul) {
-        return {value * mul, other.value};
+        return { value * mul, other.value };
     }
 
     int i{};
@@ -38,7 +46,6 @@ struct MetaCtor: ::testing::Test {
 
         entt::meta<double>()
             .type("double"_hs)
-            .conv<int>()
             .ctor<double_factory>();
 
         entt::meta<derived_t>()
@@ -51,7 +58,8 @@ struct MetaCtor: ::testing::Test {
             .ctor<const base_t &, int>()
             .ctor<const int &, char>().prop(3, false)
             .ctor<entt::overload<clazz_t(int)>(clazz_t::factory)>().prop('c', 42)
-            .ctor<entt::overload<clazz_t(base_t, int, int)>(clazz_t::factory)>();
+            .ctor<entt::overload<clazz_t(base_t, int, int)>(clazz_t::factory)>()
+            .conv<int>();
     }
 
     void TearDown() override {
@@ -136,17 +144,25 @@ TEST_F(MetaCtor, MetaAnyArgs) {
 
 TEST_F(MetaCtor, InvalidArgs) {
     auto ctor = entt::resolve<clazz_t>().ctor<int, char>();
-    ASSERT_FALSE(ctor.invoke('c', 42));
+    ASSERT_FALSE(ctor.invoke(entt::meta_any{}, derived_t{}));
 }
 
 TEST_F(MetaCtor, CastAndConvert) {
-    auto any = entt::resolve<clazz_t>().ctor<const base_t &, int>().invoke(derived_t{}, 42.);
+    auto any = entt::resolve<clazz_t>().ctor<const base_t &, int>().invoke(derived_t{}, clazz_t{42, 'd'});
 
     ASSERT_TRUE(any);
     ASSERT_EQ(any.cast<clazz_t>().i, 42);
     ASSERT_EQ(any.cast<clazz_t>().c, 'c');
 }
 
+TEST_F(MetaCtor, ArithmeticConversion) {
+    auto any = entt::resolve<clazz_t>().ctor<int, char>().invoke(true, 4.2);
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.cast<clazz_t>().i, 1);
+    ASSERT_EQ(any.cast<clazz_t>().c, char{4});
+}
+
 TEST_F(MetaCtor, ConstNonConstRefArgs) {
     int ivalue = 42;
     const char cvalue = 'c';
@@ -168,17 +184,25 @@ TEST_F(MetaCtor, FuncMetaAnyArgs) {
 
 TEST_F(MetaCtor, FuncInvalidArgs) {
     auto ctor = entt::resolve<clazz_t>().ctor<int>();
-    ASSERT_FALSE(ctor.invoke('c'));
+    ASSERT_FALSE(ctor.invoke(derived_t{}));
 }
 
 TEST_F(MetaCtor, FuncCastAndConvert) {
-    auto any = entt::resolve<clazz_t>().ctor<base_t, int, int>().invoke(derived_t{}, 3., 3);
+    auto any = entt::resolve<clazz_t>().ctor<base_t, int, int>().invoke(derived_t{}, 3., clazz_t{3, 'd'});
 
     ASSERT_TRUE(any);
     ASSERT_EQ(any.cast<clazz_t>().i, 9);
     ASSERT_EQ(any.cast<clazz_t>().c, 'c');
 }
 
+TEST_F(MetaCtor, FuncArithmeticConversion) {
+    auto any = entt::resolve<clazz_t>().ctor<int>().invoke(4.2);
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.cast<clazz_t>().i, 4);
+    ASSERT_EQ(any.cast<clazz_t>().c, 'c');
+}
+
 TEST_F(MetaCtor, FuncConstNonConstRefArgs) {
     int ivalue = 42;
     auto ctor = entt::resolve<clazz_t>().ctor<int>();

+ 8 - 5
test/entt/meta/meta_data.cpp

@@ -26,6 +26,8 @@ struct clazz_t {
           base{}
     {}
 
+    operator int() const { return h; }
+
     int i{0};
     const int j{1};
     base_t base{};
@@ -78,8 +80,7 @@ struct MetaData: ::testing::Test {
         using namespace entt::literals;
 
         entt::meta<double>()
-            .type("double"_hs)
-            .conv<int>();
+            .type("double"_hs);
 
         entt::meta<base_t>()
             .type("base"_hs)
@@ -99,7 +100,8 @@ struct MetaData: ::testing::Test {
             .data<&clazz_t::h>("h"_hs).prop(property_t::random, 2)
             .data<&clazz_t::k>("k"_hs).prop(property_t::value, 3)
             .data<&clazz_t::base>("base"_hs)
-            .data<&clazz_t::i, entt::as_void_t>("void"_hs);
+            .data<&clazz_t::i, entt::as_void_t>("void"_hs)
+            .conv<int>();
 
         entt::meta<setter_getter_t>()
             .type("setter_getter"_hs)
@@ -291,10 +293,11 @@ TEST_F(MetaData, SetConvert) {
     using namespace entt::literals;
 
     clazz_t instance{};
+    instance.h = 42;
 
     ASSERT_EQ(instance.i, 0);
-    ASSERT_TRUE(entt::resolve<clazz_t>().data("i"_hs).set(instance, 3.));
-    ASSERT_EQ(instance.i, 3);
+    ASSERT_TRUE(entt::resolve<clazz_t>().data("i"_hs).set(instance, instance));
+    ASSERT_EQ(instance.i, 42);
 }
 
 TEST_F(MetaData, SetByRef) {

+ 25 - 7
test/entt/meta/meta_func.cpp

@@ -22,7 +22,7 @@ struct base_t {
 };
 
 struct derived_t: base_t {
-    derived_t() {}
+    derived_t(): base_t{} {}
 };
 
 struct func_t {
@@ -59,6 +59,10 @@ struct func_t {
         return value;
     }
 
+    operator int() const {
+        return value;
+    }
+
     inline static int value = 0;
 };
 
@@ -67,8 +71,7 @@ struct MetaFunc: ::testing::Test {
         using namespace entt::literals;
 
         entt::meta<double>()
-            .type("double"_hs)
-            .conv<int>();
+            .type("double"_hs);
 
         entt::meta<base_t>()
             .type("base"_hs)
@@ -91,7 +94,8 @@ struct MetaFunc: ::testing::Test {
             .func<func_t::k>("k"_hs).prop(true, false)
             .func<&func_t::v, entt::as_void_t>("v"_hs)
             .func<&func_t::a, entt::as_ref_t>("a"_hs)
-            .func<&func_t::a, entt::as_cref_t>("ca"_hs);
+            .func<&func_t::a, entt::as_cref_t>("ca"_hs)
+            .conv<int>();
 
         base_t::counter = 0;
     }
@@ -157,7 +161,7 @@ TEST_F(MetaFunc, Const) {
     ASSERT_FALSE(func.arg(1u));
 
     auto any = func.invoke(instance, 4);
-    auto empty = func.invoke(instance, 'c');
+    auto empty = func.invoke(instance, derived_t{});
 
     ASSERT_FALSE(empty);
     ASSERT_TRUE(any);
@@ -231,7 +235,7 @@ TEST_F(MetaFunc, Static) {
     ASSERT_FALSE(func.arg(1u));
 
     auto any = func.invoke({}, 3);
-    auto empty = func.invoke({}, 'c');
+    auto empty = func.invoke({}, derived_t{});
 
     ASSERT_FALSE(empty);
     ASSERT_TRUE(any);
@@ -311,11 +315,25 @@ TEST_F(MetaFunc, CastAndConvert) {
     using namespace entt::literals;
 
     func_t instance;
-    auto any = entt::resolve<func_t>().func("f3"_hs).invoke(instance, derived_t{}, 0, 3.);
+    instance.value = 3;
+    auto any = entt::resolve<func_t>().func("f3"_hs).invoke(instance, derived_t{}, 0, instance);
 
     ASSERT_TRUE(any);
     ASSERT_EQ(any.type(), entt::resolve<int>());
     ASSERT_EQ(any.cast<int>(), 9);
+    ASSERT_EQ(instance.value, 0);
+}
+
+TEST_F(MetaFunc, ArithmeticConversion) {
+    using namespace entt::literals;
+
+    func_t instance;
+    auto any = entt::resolve<func_t>().func("f2"_hs).invoke(instance, true, 4.2);
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::resolve<int>());
+    ASSERT_EQ(any.cast<int>(), 16);
+    ASSERT_EQ(instance.value, 1);
 }
 
 TEST_F(MetaFunc, ArgsByRef) {

+ 14 - 9
test/entt/meta/meta_type.cpp

@@ -47,6 +47,7 @@ struct clazz_t {
 
     void member() {}
     static void func() {}
+    operator int() const { return value; }
 
     int value;
 };
@@ -94,7 +95,6 @@ struct MetaType: ::testing::Test {
 
         entt::meta<double>()
             .type("double"_hs)
-            .conv<int>()
             .data<set<double>, get<double>>("var"_hs);
 
         entt::meta<unsigned int>()
@@ -146,7 +146,8 @@ struct MetaType: ::testing::Test {
             .ctor<const base_t &, int>()
             .data<&clazz_t::value>("value"_hs)
             .func<&clazz_t::member>("member"_hs)
-            .func<clazz_t::func>("func"_hs);
+            .func<clazz_t::func>("func"_hs)
+            .conv<int>();
     }
 
     void TearDown() override {
@@ -334,9 +335,6 @@ TEST_F(MetaType, Invoke) {
 TEST_F(MetaType, OverloadedFunc) {
     using namespace entt::literals;
 
-    entt::meta<float>().conv<int>();
-    entt::meta<double>().conv<float>();
-
     const auto type = entt::resolve<overloaded_func_t>();
     overloaded_func_t instance{};
 
@@ -421,7 +419,7 @@ TEST_F(MetaType, ConstructMetaAnyArgs) {
 }
 
 TEST_F(MetaType, ConstructInvalidArgs) {
-    ASSERT_FALSE(entt::resolve<clazz_t>().construct(base_t{}, 'c'));
+    ASSERT_FALSE(entt::resolve<clazz_t>().construct('c', base_t{}));
 }
 
 TEST_F(MetaType, LessArgs) {
@@ -429,12 +427,19 @@ TEST_F(MetaType, LessArgs) {
 }
 
 TEST_F(MetaType, ConstructCastAndConvert) {
-    auto any = entt::resolve<clazz_t>().construct(derived_t{}, 42.);
+    auto any = entt::resolve<clazz_t>().construct(derived_t{}, clazz_t{derived_t{}, 42});
 
     ASSERT_TRUE(any);
     ASSERT_EQ(any.cast<clazz_t>().value, 42);
 }
 
+TEST_F(MetaType, ConstructArithmeticConversion) {
+    auto any = entt::resolve<clazz_t>().construct(derived_t{}, clazz_t{derived_t{}, true});
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.cast<clazz_t>().value, 1);
+}
+
 TEST_F(MetaType, Reset) {
     using namespace entt::literals;
 
@@ -599,11 +604,11 @@ TEST_F(MetaType, ResetAndReRegistrationAfterReset) {
     ASSERT_FALSE(entt::resolve<clazz_t>().data("value"_hs));
     ASSERT_FALSE(entt::resolve<clazz_t>().func("member"_hs));
 
-    entt::meta<double>().type("double"_hs).conv<float>();
+    entt::meta<double>().type("double"_hs);
     entt::meta_any any{42.};
 
     ASSERT_TRUE(any);
-    ASSERT_FALSE(any.allow_cast<int>());
+    ASSERT_TRUE(any.allow_cast<int>());
     ASSERT_TRUE(any.allow_cast<float>());
 
     ASSERT_FALSE(entt::resolve("derived"_hs));