Browse Source

[meta] Meta-function overload support (#538)

Now meta-functions can be overloaded. When a function with a duplicate
identifier is registered, it's added into the meta-type function list after the
first meta-function with the same id. During the invocation, the meta-functions
with the requested id are iterated, and the best matching one is chosen. The
overload that doesn't require any type conversions or the minimal number of
those is considered the best match. If none of the overloads matches the given
arguments or if there are multiple matching overloads requiring the same number
of type casts then the invocation will fail the same way it happens in C++.

Signed-off-by: Innokentiy Alaytsev <alaitsev@gmail.com>
Innokentiy Alaytsev 5 years ago
parent
commit
24f937fe4f
3 changed files with 162 additions and 7 deletions
  1. 20 5
      src/entt/meta/factory.hpp
  2. 52 2
      src/entt/meta/meta.hpp
  3. 90 0
      test/entt/meta/meta_type.cpp

+ 20 - 5
src/entt/meta/factory.hpp

@@ -675,8 +675,8 @@ public:
         static internal::meta_func_node node{
             {},
             type,
-            nullptr,
-            nullptr,
+            nullptr, // parent
+            nullptr, // next
             std::tuple_size_v<typename helper_type::args_type>,
             helper_type::is_const,
             helper_type::is_static,
@@ -687,11 +687,26 @@ public:
             }
         };
 
-        ENTT_ASSERT(!exists(id, type->func));
         ENTT_ASSERT(!exists(&node, type->func));
+
+        if (exists(id, type->func)) {
+            auto const existing_overload =
+                internal::find_if<&std::decay_t<decltype(*type)>::func>(
+                    [id](const auto* curr) {
+                        return id == curr->id;
+                    }, type);
+
+            ENTT_ASSERT(existing_overload);
+
+            node.next = existing_overload->next;
+            existing_overload->next = &node;
+        }
+        else {
+            node.next = type->func;
+            type->func = &node;
+        }
+
         node.id = id;
-        node.next = type->func;
-        type->func = &node;
 
         return meta_factory<Type, std::integral_constant<decltype(Candidate), Candidate>>{&node.prop};
     }

+ 52 - 2
src/entt/meta/meta.hpp

@@ -1368,8 +1368,58 @@ public:
      * @return A meta any containing the returned value, if any.
      */
     meta_any invoke(const id_type id, meta_handle instance, meta_any * const args, const std::size_t sz) const {
-        auto const candidate = func(id);
-        return candidate ? candidate.invoke(std::move(instance), args, sz) : meta_any{};
+        const internal::meta_func_node* best_match{ nullptr };
+        internal::meta_func_node::size_type best_match_casts_required{0};
+
+        for (auto candidate = internal::find_if<&node_type::func>([id](const auto *curr) { return curr->id == id; }, node); candidate && (id == candidate->id); candidate = candidate->next) {
+            if (sz != candidate->size) {
+                continue;
+            }
+
+            node_type::size_type casts_required{ 0 };
+            bool match{ true };
+
+            for (node_type::size_type arg_idx = 0; arg_idx < sz; ++arg_idx) {
+                auto const& candidate_arg_type_info = candidate->arg (arg_idx)->info;
+                auto const arg_type = (args + arg_idx)->type();
+
+				ENTT_ASSERT(bool(candidate_arg_type_info));
+
+                if (candidate_arg_type_info == arg_type.info()) {
+                    continue;
+                }
+
+                if (auto const arg_convs = arg_type.conv();
+                    arg_convs.end () != std::find_if(
+                        arg_convs.begin(),
+                        arg_convs.end(),
+                        [candidate_arg_type_info](auto&& curr) {
+                            return curr.type().info() == candidate_arg_type_info;
+                        })) {
+                    casts_required++;
+                }
+                else {
+                    match = false;
+                    break;
+                }
+
+            }
+
+            if (!match) {
+                continue;
+            }
+
+            if ((0 < best_match_casts_required) && (casts_required == best_match_casts_required)) {
+                return meta_any{};
+            }
+
+            if (!best_match || (casts_required < best_match_casts_required)) {
+                best_match = candidate;
+                best_match_casts_required = casts_required;
+            }
+        }
+
+        return best_match ? best_match->invoke (instance, args) : meta_any{};
     }
 
     /**

+ 90 - 0
test/entt/meta/meta_type.cpp

@@ -48,6 +48,36 @@ struct clazz_t {
     int value;
 };
 
+struct overloaded_func_t {
+    void e (int v) {
+        value = v + v;
+    }
+
+    int f(const base_t &, int a, int b) {
+        return f(a, b);
+    }
+
+    int f(int a, int b) {
+        value = a;
+        return b*b;
+    }
+
+    int f(int v) const {
+        return v*v;
+    }
+
+    float f (int a, float b) {
+        value = a;
+        return b + b;
+    }
+
+    void g (int v) {
+        value = v * v;
+    }
+
+    inline static int value = 0;
+};
+
 enum class property_t {
     random,
     value,
@@ -69,6 +99,14 @@ struct MetaType: ::testing::Test {
         entt::meta<abstract_t>().func<&abstract_t::func>("func"_hs);
         entt::meta<concrete_t>().base<base_t>().base<abstract_t>();
 
+        entt::meta<overloaded_func_t>().type("overloaded_func"_hs)
+                .func<&overloaded_func_t::e> ("e"_hs)
+                .func<entt::overload<int(const base_t &, int, int)>(&overloaded_func_t::f)>("f"_hs)
+                .func<entt::overload<int(int, int)>(&overloaded_func_t::f)>("f"_hs)
+                .func<entt::overload<int(int) const>(&overloaded_func_t::f)>("f"_hs)
+                .func<entt::overload<float (int, float)> (&overloaded_func_t::f)> ("f"_hs)
+                .func<&overloaded_func_t::g> ("g"_hs);
+
         entt::meta<property_t>()
             .data<property_t::random>("random"_hs)
                 .prop(property_t::random, 0)
@@ -287,6 +325,57 @@ TEST_F(MetaType, Invoke) {
     ASSERT_FALSE(type.invoke("rebmem"_hs, {}));
 }
 
+TEST_F(MetaType, OverloadedFunc) {
+    entt::meta<float> ().conv<int>();
+    entt::meta<double>().conv<float>();
+
+    auto type = entt::resolve<overloaded_func_t>();
+    overloaded_func_t instance{};
+
+    ASSERT_TRUE(type.func("f"_hs));
+    ASSERT_TRUE(type.func("e"_hs));
+    ASSERT_TRUE(type.func("g"_hs));
+
+    auto const first_overload_result = type.invoke("f"_hs, instance, base_t{}, 1, 2);
+
+    ASSERT_TRUE(first_overload_result);
+    ASSERT_EQ (overloaded_func_t::value, 1);
+    ASSERT_TRUE (first_overload_result.try_cast<int>());
+    ASSERT_EQ (first_overload_result.cast<int>(), 4);
+
+    auto const second_overload_result = type.invoke("f"_hs, instance, 3, 4);
+
+    ASSERT_TRUE (second_overload_result);
+    ASSERT_EQ (overloaded_func_t::value, 3);
+    ASSERT_TRUE (second_overload_result.try_cast<int>());
+    ASSERT_EQ (second_overload_result.cast<int> (), 16);
+
+    auto const third_overload_result = type.invoke("f"_hs, instance, 5);
+
+    ASSERT_TRUE (third_overload_result);
+    ASSERT_EQ (overloaded_func_t::value, 3);
+    ASSERT_TRUE (third_overload_result.try_cast<int>());
+    ASSERT_EQ (third_overload_result.cast<int> (), 25);
+
+    auto const fourth_overload_result = type.invoke("f"_hs, instance, 6, 7.0f);
+
+    ASSERT_TRUE (fourth_overload_result);
+    ASSERT_EQ (overloaded_func_t::value, 6);
+    ASSERT_TRUE (fourth_overload_result.try_cast<float>());
+    ASSERT_EQ (fourth_overload_result.cast<float> (), 14.0f);
+
+    auto const overload_with_cast_result = type.invoke("f"_hs, instance, 8, 9.0f);
+
+    ASSERT_TRUE (overload_with_cast_result);
+    ASSERT_EQ (overloaded_func_t::value, 8);
+    ASSERT_TRUE (overload_with_cast_result.try_cast<float>());
+    ASSERT_EQ (overload_with_cast_result.cast<float> (), 18);
+
+    auto const ambiguous_overload_result = type.invoke("f"_hs, instance, 8, 9.0);
+
+    ASSERT_FALSE (ambiguous_overload_result);
+}
+
 TEST_F(MetaType, SetGet) {
     auto type = entt::resolve<clazz_t>();
     clazz_t instance{};
@@ -442,6 +531,7 @@ TEST_F(MetaType, ResetAndReRegistrationAfterReset) {
     entt::resolve<derived_t>().reset();
     entt::resolve<abstract_t>().reset();
     entt::resolve<concrete_t>().reset();
+    entt::resolve<overloaded_func_t> ().reset ();
     entt::resolve<property_t>().reset();
     entt::resolve<clazz_t>().reset();