Browse Source

reflection system

Michele Caini 7 years ago
parent
commit
618a325057

+ 1 - 1
.travis.yml

@@ -51,7 +51,7 @@ install:
 
 script:
 - mkdir -p build && cd build
-- cmake .. && make -j4
+- cmake -DCMAKE_BUILD_TYPE=Release .. && make -j4
 - CTEST_OUTPUT_ON_FAILURE=1 make test
 
 deploy:

+ 22 - 20
README.md

@@ -52,23 +52,25 @@ a while the codebase has grown and more and more classes have become part of the
 repository.<br/>
 Here is a brief, yet incomplete list of what it offers today:
 
-* Statically generated integer identifiers for types (assigned either at
-  compile-time or at runtime).
-* A constexpr utility for human readable resource identifiers.
-* A minimal configuration system built on top of the monostate pattern.
-* An incredibly fast entity-component system based on sparse sets, with its own
-  views and a _pay for what you use_ policy to adjust performance and memory
+* Statically generated integer **identifiers for types** (assigned either at
+  **compile-time** or at **runtime**).
+* A **constexpr utility** for **human readable resource identifiers**.
+* A minimal **configuration system** built on top of the **monostate pattern**.
+* **An incredibly fast entity-component system** based on sparse sets, with its
+  own views and a _pay for what you use_ policy to adjust performance and memory
   usage according to users' requirements.
-* A lot of facilities built on top of the entity-component system to help
-  developers and avoid reinventing the wheel (ie dependencies, snapshot, actor
-  class for those who aren't confident with the architecture and so on).
-* The smallest and most basic implementation of a service locator ever seen.
-* A cooperative scheduler for processes of any type.
-* All what is needed for resource management (cache, loaders, handles).
-* Delegates, signal handlers (with built-in support for collectors) and a tiny
-  event dispatcher.
-* A general purpose event emitter, that is a CRTP idiom based class template.
-* An event dispatcher for immediate and delayed events to integrate in loops.
+* A lot of **facilities built on top of the entity-component system** to help
+  developers and avoid reinventing the wheel (ie **dependencies**, **snapshot**,
+  **actor class** for those who aren't confident with the architecture and so
+  on).
+* The smallest and most basic implementation of a **service locator** ever seen.
+* A built-in, non-intrusive and macro-free **runtime reflection system**.
+* A **cooperative scheduler** for processes of any type.
+* All what is needed for **resource management** (cache, loaders, handles).
+* **Delegates**, **signal handlers** (with built-in support for collectors) and
+  a tiny **event dispatcher** for immediate and delayed events to integrate in
+  loops.
+* A general purpose **event emitter** as a CRTP idiom based class template.
 * ...
 * Any other business.
 
@@ -257,10 +259,10 @@ The API reference will be created in HTML format within the directory
 <!--
 @cond TURN_OFF_DOXYGEN
 -->
-The API reference is also available [online](https://skypjack.github.io/entt/)
-for the latest version.<br/>
-There exists also a [wiki](https://github.com/skypjack/entt/wiki) dedicated to
-the project where users can find all related documentation pages.
+It's also available [online](https://skypjack.github.io/entt/) for the latest
+version.<br/>
+Finally, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
+to the project where users can find all related documentation pages.
 <!--
 @endcond TURN_OFF_DOXYGEN
 -->

+ 12 - 2
TODO

@@ -1,13 +1,23 @@
+* suggestion/request: cache made with flat_map + object pools + new/delete overloads so as to get rid of shared pointers (+ cache coherency)
+# suggestion/request: add iterative sorter to the algorithms
 * custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
 * scene management (I prefer the concept of spaces, that is a kind of scene anyway)
 * debugging tools (#60): the issue online already contains interesting tips on this, look at it
 * define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
 * define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
 * registry::create with a "hint" on the entity identifier to use, it should ease combining multiple registries
+* optimize for empty components, it would be a mid improvement in terms of memory usage (see std::is_empty)
 * deep copy of a registry (or use the snapshot stuff to copy components and keep intact ids at least)
 * is it possible to iterate all the components assigned to an entity through a common base class?
-* optimize for empty components, it would be a mid improvement in terms of memory usage
+* prototype should offer a way to retrieve its internal registry
 * can we do more for shared libraries? who knows... see #144
 * work stealing job system (see #100)
-* reflection system (maybe)
 * composable looper so as to pack erased systems, compose runners at different rates and run them at once in the loop
+* meta: sort of meta view based on meta stuff to iterate entities, void * and meta info objects
+* registry::probe<component>(entt) (returns a component * if entt has the component, nullptr otherwise)
+* hashed string: add implicit check on construction for uniqueness (optional)
+* add a note about multithreading support to the README file
+* signals on entity creation/destruction
+* flexible views with "composable" filters
+* do not set BUILD_TESTING to ON by default anymore
+* add https://github.com/Kerndog73/EnTT-Pacman to entt in action

+ 1 - 0
docs/CMakeLists.txt

@@ -28,6 +28,7 @@ add_custom_target(
         core.md
         entity.md
         locator.md
+        meta.md
         process.md
         resource.md
         shared.md

+ 339 - 0
docs/meta.md

@@ -0,0 +1,339 @@
+# Crash Course: meta
+
+<!--
+@cond TURN_OFF_DOXYGEN
+-->
+# Table of Contents
+
+* [Introduction](#introduction)
+* [Reflection in a nutshell](#reflection-in-a-nutshell)
+* [Any as in any type](#any-as-in-any-type)
+* [Enjoy the runtime](#enjoy-the-runtime)
+* [Properties and meta objects](#properties-and-meta-objects)
+<!--
+@endcond TURN_OFF_DOXYGEN
+-->
+
+# Introduction
+
+Reflection (or rather, its lack) is a trending topic in the C++ world and, in
+the specific case of `EnTT`, a tool that can unlock a lot of other features. I
+looked for a third-party library that met my needs on the subject, but I always
+came across some details that I didn't like: macros, being intrusive, too many
+allocations. In one word: unsatisfactory.<br/>
+I finally decided to write a built-in, non-intrusive and macro-free runtime
+reflection system for `EnTT. Maybe I didn't do better than others or maybe yes,
+time will tell me, but at least I can model this tool around the library to
+which it belongs and not vice versa.
+
+# Reflection in a nutshell
+
+Reflection always starts from real types (users cannot reflect imaginary types
+and it would not make much sense, we wouldn't be talking about reflection
+anymore).<br/>
+To _reflect_ a type, the library provides the `reflect` function:
+
+```cpp
+auto factory = entt::reflect<my_type>("reflected_type");
+```
+
+It accepts the type to reflect as a template parameter and an optional name as
+an argument. Names are important because users can retrieve meta types at
+runtime by searching for them by name. However, there are cases in which users
+can be interested in adding features to a reflected type so that the reflection
+system can use it correctly under the hood, but they don't want to allow
+searching the type by name.<br/>
+In both cases, the returned value is a factory object to use to continue
+building the meta type.
+
+A factory is such that all its member functions returns the factory itself.
+It can be used to extend the reflected type and add the following:
+
+* _Constructors_. Actual constructors can be assigned to a reflected type by
+  specifying their list of arguments. Free functions (namely, factories) can be
+  used as well, as long as the return type is the expected one. From a client's
+  point of view, nothing changes if a constructor is a free function or an
+  actual constructor.<br/>
+  Use the `ctor` member function for this purpose:
+
+  ```cpp
+  entt::reflect<my_type>("reflected").ctor<int, char>().ctor<&factory>();
+  ```
+
+* _Destructors_. Free functions can be set as destructors of reflected types.
+  The purpose is to give users the ability to free up resources that require
+  special treatment  before an object is actually destroyed.<br/>
+  Use the `dtor` member function for this purpose:
+
+  ```cpp
+  entt::reflect<my_type>("reflected").dtor<&destroy>();
+  ```
+
+* _Data members_. Both real data members of the underlying type and static or
+  global variables can be attached to a meta type. From a client's point of
+  view, all the variables associated with the reflected type will appear as if
+  they were part of the type.<br/>
+  Use the `data` member function for this purpose:
+
+  ```cpp
+  entt::reflect<my_type>("reflected")
+      .data<&my_type::static_variable>("static")
+      .data<&my_type::data_member>("member")
+      .data<&global_variable>("global");
+  ```
+
+  This function requires as an argument the name to give to the meta data once
+  created. Users can then access meta data at runtime by searching for them by
+  name.
+
+* _Member functions_. Both real member functions of the underlying type and free
+  functions can be attached to a meta type. From a client's point of view, all
+  the functions associated with the reflected type will appear as if they were
+  part of the type.<br/>
+  Use the `func` member function for this purpose:
+
+  ```cpp
+  entt::reflect<my_type>("reflected")
+      .func<&my_type::static_function>("static")
+      .func<&my_type::member_function>("member")
+      .func<&free_function>("free");
+  ```
+
+  This function requires as an argument the name to give to the meta function
+  once created. Users can then access meta functions at runtime by searching for
+  them by name.
+
+* _Base classes_. A base class is such that the underlying type is actually
+  derived from it. In this case, the reflection system tracks the relationship
+  and allows for implicit casts at runtime when required.<br/>
+  Use the `base` member function for this purpose:
+
+  ```cpp
+  entt::reflect<derived_type>("derived").base<base_type>();
+  ```
+
+  From now on, wherever a `my_base_type` is required, an instance of `my_type`
+  will also be accepted.
+
+* _Conversion functions_. Actual types can be converted, this is a fact. Just
+  think of the relationship between a `double` and an `int` to see it. Similar
+  to bases, conversion functions allow users to define conversions that will be
+  implicitly performed by the reflection system when required.<br/>
+  Use the `conv` member function for this purpose:
+
+  ```cpp
+  entt::reflect<double>().conv<int>();
+  ```
+
+That's all, everything users need to create meta types and enjoy the reflection
+system. At first glance it may not seem that much, but users usually learn to
+appreciate it over time.<br/>
+Also, do not forget what these few lines hide under the hood: a built-in,
+non-intrusive and macro-free system for reflection in C++. Features that are
+definitely worth the price, at least for me.
+
+# Any as in any type
+
+The reflection system comes with its own meta any type. It may seem redundant
+since C++17 introduced `std::any`, but it is not.<br/>
+In fact, the _type_ returned by an `std::any` is a const reference to an
+`std::type_info`, an implementation defined class that's not something everyone
+wants to see in a software. Furthermore, the class `std::type_info` suffers from
+some design flaws and there is even no way to _convert_ an `std::type_info` into
+a meta type, thus linking the two worlds.
+
+A meta any object provides an API similar to that of its most famous counterpart
+and serves the same purpose of being an opaque container for any type of
+value.<br/>
+It minimizes the allocations required, which are almost absent thanks to _SBO_
+techniques. In fact, unless users deal with _fat types_ and create instances of
+them though the reflection system, allocations are at zero.
+
+A meta any object can be created by any other object or as an empty container
+to initialize later:
+
+```cpp
+// a meta any object that contains an int
+entt::meta_any any{0};
+
+// an empty meta any object
+entt::meta_any empty{};
+```
+
+It can be constructed or assigned by copy and move and it takes the burden of
+destroying the contained object when required.<br/>
+A meta any object has a `type` member function that returns a pointer to the
+meta type of the contained value, if any. The member functions `can_cast` and
+`can_convert` are used to know if the underlying object has a given type as a
+base or if it can be converted implicitly to it. Similarly, `cast` and `convert`
+do what they promise and return the expected value.
+
+# Enjoy the runtime
+
+Once the web of reflected types has been constructed, it's a matter of using it
+at runtime where required.<br/>
+All this has the great merit that, unlike the vast majority of the things
+present in this library and closely linked to the compile-time, the reflection
+system stands in fact as a non-intrusive tool for the runtime.
+
+To search for a reflected type there are two options: by type or by name. In
+both cases, the search can be done by means of the `resolve` function:
+
+```cpp
+// search for a reflected type by type
+auto *by_type = entt::resolve<my_type>();
+
+// search for a reflected type by name
+auto *by_name = entt::resolve("reflected_type");
+```
+
+There exits also a third overload of the `resolve` function to use to iterate
+all the reflected types at once:
+
+```cpp
+resolve([](auto *type) {
+    // ...
+});
+```
+
+In all cases, the returned value is a pointer to a `meta_type` object. This type
+of objects offer an API to know the _runtime name_ of the type, to iterate all
+the meta objects associated with them and even to build or destroy instances of
+the underlying type.<br/>
+Refer to the inline documentation for all the details.
+
+The meta objects that compose a meta type are accessed in the following ways:
+
+* _Meta constructors_. They are accessed by types of arguments:
+
+  ```cpp
+  auto *ctor = entt::resolve<my_type>().ctor<int, char>();
+  ```
+
+  The returned type is `meta_ctor *` and may be null if there is no constructor
+  that accepts the supplied arguments or at least some types from which they are
+  derived or to which they can be converted.<br/>
+  A meta constructor offers an API to know the number of arguments, the expected
+  meta types and to invoke it, therefore to construct a new instance of the
+  underlying type.
+
+* _Meta destructor_. It's returned by a dedicated function:
+
+  ```cpp
+  auto *dtor = entt::resolve<my_type>().dtor();
+  ```
+
+  The returned type is `meta_dtor *` and may be null if there is no custom
+  destructor set for the gven meta type.<br/>
+  All what a meta destructor has to offer is a way to invoke it on a given
+  instance. Be aware that the result may not be what is expected.
+
+* _Meta data_. They are accessed by name:
+
+  ```cpp
+  auto *data = entt::resolve<my_type>().data("member");
+  ```
+
+  The returned type is `meta_data *` and may be null if there is no meta data
+  object associated with the given name.<br/>
+  A meta data object offers an API to query the underlying type (ie to know if
+  it's a const or a static one), to get the meta type of the variable and to set
+  or get the contained value.
+
+* _Meta functions_. They are accessed by name:
+
+  ```cpp
+  auto *func = entt::resolve<my_type>().func("member");
+  ```
+
+  The returned type is `meta_func *` and may be null if there is no meta
+  function object associated with the given name.<br/>
+  A meta function object offers an API to query the underlying type (ie to know
+  if it's a const or a static function), to know the number of arguments, the
+  meta return type and the meta types of the parameters. In addition, a meta
+  function object can be used to invoke the underlying function and then get the
+  return value in the form of meta any object.
+
+
+* _Meta bases_. They are accessed through the name of the base types:
+
+  ```cpp
+  auto *base = entt::resolve<derived_type>().base("base");
+  ```
+
+  The returned type is `meta_base *` and may be null if there is no meta base
+  object associated with the given name.<br/>
+  Meta bases aren't meant to be used directly, even though they are freely
+  accessible. They expose only a few methods to use to know the meta type of the
+  base class and to convert a raw pointer between types.
+
+* _Meta conversion functions_. They are accessed by type:
+
+  ```cpp
+  auto *conv = entt::resolve<double>().conv<int>();
+  ```
+
+  The returned type is `meta_conv *` and may be null if there is no meta
+  conversion function associated with the given type.<br/>
+  The meta conversion functions are as thin as the meta bases and with a very
+  similar interface. The sole difference is that they return a newly created
+  instance wrapped in a meta any object when they convert between different
+  types.
+
+Furthermore, all meta objects with the exception of meta destructors can be
+iterated through an overload that accepts a callback through which to return
+them. As an example:
+
+```cpp
+entt::resolve<my_type>().data([](auto *data) {
+    // ...
+});
+```
+
+A meta type can also be used to `construct` or `destroy` actual instances of the
+underlying type.<br/>
+In particular, the `construct` member function accepts a variable number of
+arguments and searches for a match. It returns a `meta_any` object that may or
+may not be initialized, depending on whether a suitable constructor has been
+found or not. On the other side, the `destroy` member function accepts instances
+of `meta_any` as well as actual objects by reference and invokes the registered
+destructor if any or a default one.<br/>
+Be aware that the result of a call to `destroy` may not be what is expected.
+
+Meta types and meta objects in general contain much more than what is said: a
+plethora of functions in addition to those listed whose purposes and uses go
+unfortunately beyond the scope of this document.<br/>
+I invite anyone interested in the subject to look at the code, experiment and
+read the official documentation to get the best out of this powerful tool.
+
+# Properties and meta objects
+
+Sometimes (ie when it comes to creating an editor) it might be useful to be able
+to attach properties to the meta objects created. Fortunately, this is possible
+for most of them.<br/>
+To attach a property to a meta object, no matter what as long as it supports
+properties, it is sufficient to provide an object at the time of construction
+such that `std::get<0>` and `std::get<1>` are valid for it. In other terms, the
+properties are nothing more than key/value pairs users can put in an
+`std::pair`. As an example:
+
+```cpp
+entt::reflect<my_type>("reflected", std::make_pair("tooltip"_hs, "message"));
+```
+
+The meta objects that support properties offer then a couple of member functions
+named `prop` to iterate them at once and to search a specific property by key:
+
+```cpp
+// iterate all the properties of a meta type
+entt::resolve<my_type>().prop([](auto *prop) {
+    // ...
+});
+
+// search for a given property by name
+auto *prop = entt::resolve<my_type>().prop("tooltip"_hs);
+```
+
+Meta properties are objects having a fairly poor interface, all in all. They
+only provide the `key` and the `value` member functions to be used to retrieve
+the key and the value contained in the form of meta any objects, respectively.

+ 0 - 1
src/entt/config/config.h

@@ -12,5 +12,4 @@
 #endif // ENTT_HS_SUFFIX
 
 
-
 #endif // ENTT_CONFIG_CONFIG_H

+ 0 - 33
src/entt/core/algorithm.hpp

@@ -72,39 +72,6 @@ struct insertion_sort final {
 };
 
 
-/*! @brief Function object for performing bubble sort (single iteration). */
-struct one_shot_bubble_sort final {
-    /**
-     * @brief Tries to sort the elements in a range.
-     *
-     * Performs a single iteration to sort the elements in a range using the
-     * given binary comparison function. The range may not be completely sorted
-     * after running this function.
-     *
-     * @tparam It Type of random access iterator.
-     * @tparam Compare Type of comparison function object.
-     * @param first An iterator to the first element of the range to sort.
-     * @param last An iterator past the last element of the range to sort.
-     * @param compare A valid comparison function object.
-     */
-    template<typename It, typename Compare = std::less<>>
-    void operator()(It first, It last, Compare compare = Compare{}) const {
-        if(first != last) {
-            auto it = first++;
-
-            while(first != last) {
-                if(compare(*first, *it)) {
-                    using std::swap;
-                    std::swap(*first, *it);
-                }
-
-                it = first++;
-            }
-        }
-    }
-};
-
-
 }
 
 

+ 6 - 1
src/entt/core/hashed_string.hpp

@@ -30,7 +30,7 @@ class hashed_string final {
     static constexpr std::uint64_t prime = 1099511628211ull;
 
     // Fowler–Noll–Vo hash function v. 1a - the good
-    static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) ENTT_NOEXCEPT {
+    inline static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) ENTT_NOEXCEPT {
         return str[0] == 0 ? partial : helper((partial^str[0])*prime, str+1);
     }
 
@@ -38,6 +38,11 @@ public:
     /*! @brief Unsigned integer type. */
     using hash_type = std::uint64_t;
 
+    /*! @brief Constructs an empty hashed string. */
+    constexpr hashed_string() ENTT_NOEXCEPT
+        : hash{}, str{nullptr}
+    {}
+
     /**
      * @brief Constructs a hashed string from an array of const chars.
      *

+ 32 - 0
src/entt/core/utility.hpp

@@ -0,0 +1,32 @@
+#ifndef ENTT_CORE_UTILITY_HPP
+#define ENTT_CORE_UTILITY_HPP
+
+
+namespace entt {
+
+
+/**
+ * @brief Constant utility to disambiguate overloaded member functions.
+ * @tparam Type Function type of the desired overload.
+ * @tparam Class Type of class to which the member functions belong.
+ * @param member A valid pointer to a member function.
+ * @return Pointer to the member function.
+ */
+template<typename Type, typename Class>
+constexpr auto overload(Type Class:: *member) { return member; }
+
+
+/**
+ * @brief Constant utility to disambiguate overloaded functions.
+ * @tparam Type Function type of the desired overload.
+ * @param func A valid pointer to a function.
+ * @return Pointer to the function.
+ */
+template<typename Type>
+constexpr auto overload(Type *func) { return func; }
+
+
+}
+
+
+#endif // ENTT_CORE_UTILITY_HPP

+ 6 - 6
src/entt/entity/registry.hpp

@@ -103,7 +103,7 @@ class registry {
 
     template<typename Component>
     inline component_pool<Component> & pool() ENTT_NOEXCEPT {
-        return const_cast<component_pool<Component> &>(const_cast<const registry *>(this)->pool<Component>());
+        return const_cast<component_pool<Component> &>(std::as_const(*this).template pool<Component>());
     }
 
     template<typename Comp, std::size_t Index, typename... Component, std::size_t... Indexes>
@@ -156,7 +156,7 @@ public:
     using sink_type = typename signal_type::sink_type;
 
     /*! @brief Default constructor. */
-    registry() = default;
+    registry() ENTT_NOEXCEPT = default;
 
     /*! @brief Copying a registry isn't allowed. */
     registry(const registry &) = delete;
@@ -759,7 +759,7 @@ public:
      * comparison function should be equivalent to the following:
      *
      * @code{.cpp}
-     * bool(const Component &, const Component &)
+     * bool(const Component &, const Component &);
      * @endcode
      *
      * Moreover, the comparison function object shall induce a
@@ -1235,11 +1235,11 @@ public:
     snapshot_loader<Entity> loader() ENTT_NOEXCEPT {
         using assure_fn_type = void(registry &, const entity_type, const bool);
 
-        assure_fn_type *assure = [](registry &reg, const entity_type entity, const bool destroyed) {
+        assure_fn_type *assure = [](registry &registry, const entity_type entity, const bool destroyed) {
             using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
             // explicit promotion to avoid warnings with std::uint16_t
             const auto entt = promotion_type{entity} & traits_type::entity_mask;
-            auto &entities = reg.entities;
+            auto &entities = registry.entities;
 
             if(!(entt < entities.size())) {
                 auto curr = entities.size();
@@ -1250,7 +1250,7 @@ public:
             entities[entt] = entity;
 
             if(destroyed) {
-                reg.destroy(entity);
+                registry.destroy(entity);
                 const auto version = entity & (traits_type::version_mask << traits_type::entity_shift);
                 entities[entt] = ((entities[entt] & traits_type::entity_mask) | version);
             }

+ 6 - 6
src/entt/entity/snapshot.hpp

@@ -200,11 +200,11 @@ class snapshot_loader final {
     /*! @brief A registry is allowed to create snapshot loaders. */
     friend class registry<Entity>;
 
-    using assure_fn_type = void(registry<Entity> &, const Entity, const bool);
+    using force_fn_type = void(registry<Entity> &, const Entity, const bool);
 
-    snapshot_loader(registry<Entity> &reg, assure_fn_type *assure_fn) ENTT_NOEXCEPT
+    snapshot_loader(registry<Entity> &reg, force_fn_type *force) ENTT_NOEXCEPT
         : reg{reg},
-          assure_fn{assure_fn}
+          force{force}
     {
         // restore a snapshot as a whole requires a clean registry
         assert(!reg.capacity());
@@ -218,7 +218,7 @@ class snapshot_loader final {
         while(length--) {
             Entity entity{};
             archive(entity);
-            assure_fn(reg, entity, destroyed);
+            force(reg, entity, destroyed);
         }
     }
 
@@ -232,7 +232,7 @@ class snapshot_loader final {
             Type instance{};
             archive(entity, instance);
             static constexpr auto destroyed = false;
-            assure_fn(reg, entity, destroyed);
+            force(reg, entity, destroyed);
             reg.template assign<Type>(args..., entity, std::as_const(instance));
         }
     }
@@ -321,7 +321,7 @@ public:
 
 private:
     registry<Entity> &reg;
-    assure_fn_type *assure_fn;
+    force_fn_type *force;
 };
 
 

+ 1 - 1
src/entt/entity/sparse_set.hpp

@@ -976,7 +976,7 @@ public:
      * comparison function should be equivalent to the following:
      *
      * @code{.cpp}
-     * bool(const Type &, const Type &)
+     * bool(const Type &, const Type &);
      * @endcode
      *
      * Moreover, the comparison function object shall induce a

+ 3 - 0
src/entt/entt.hpp

@@ -3,6 +3,7 @@
 #include "core/hashed_string.hpp"
 #include "core/ident.hpp"
 #include "core/monostate.hpp"
+#include "core/utility.hpp"
 #include "entity/actor.hpp"
 #include "entity/entity.hpp"
 #include "entity/entt_traits.hpp"
@@ -13,6 +14,8 @@
 #include "entity/sparse_set.hpp"
 #include "entity/view.hpp"
 #include "locator/locator.hpp"
+#include "meta/factory.hpp"
+#include "meta/meta.hpp"
 #include "process/process.hpp"
 #include "process/scheduler.hpp"
 #include "resource/cache.hpp"

+ 480 - 0
src/entt/meta/factory.hpp

@@ -0,0 +1,480 @@
+#ifndef ENTT_META_FACTORY_HPP
+#define ENTT_META_FACTORY_HPP
+
+
+#include <cassert>
+#include <utility>
+#include <algorithm>
+#include <type_traits>
+#include "../core/hashed_string.hpp"
+#include "meta.hpp"
+
+
+namespace entt {
+
+
+template<typename>
+class meta_factory;
+
+
+template<typename Type, typename... Property>
+meta_factory<Type> reflect(const char *str, Property &&... property) ENTT_NOEXCEPT;
+
+
+/**
+ * @brief A meta factory to be used for reflection purposes.
+ *
+ * A meta factory is an utility class used to reflect types, data and functions
+ * of all sorts. This class ensures that the underlying web of types is built
+ * correctly and performs some checks in debug mode to ensure that there are no
+ * subtle errors at runtime.
+ *
+ * @tparam Type Reflected type for which the factory was created.
+ */
+template<typename Type>
+class meta_factory {
+    static_assert((std::is_scalar_v<Type> || std::is_class_v<Type>) && !std::is_const_v<Type>);
+
+    template<auto Data>
+    static std::enable_if_t<std::is_member_object_pointer_v<decltype(Data)>, decltype(std::declval<Type>().*Data)>
+    actual_type();
+
+    template<auto Data>
+    static std::enable_if_t<!std::is_member_object_pointer_v<decltype(Data)>, decltype(*Data)>
+    actual_type();
+
+    template<auto Data>
+    using data_type = std::remove_reference_t<decltype(meta_factory::actual_type<Data>())>;
+
+    template<auto Func>
+    using func_type = internal::meta_function_helper<std::integral_constant<decltype(Func), Func>>;
+
+    template<typename Node>
+    inline bool duplicate(const hashed_string &name, const Node *node) ENTT_NOEXCEPT {
+        return node ? node->name == name || duplicate(name, node->next) : false;
+    }
+
+    inline bool duplicate(const meta_any &key, const internal::meta_prop_node *node) ENTT_NOEXCEPT {
+        return node ? node->key() == key || duplicate(key, node->next) : false;
+    }
+
+    template<typename>
+    internal::meta_prop_node * properties() {
+        return nullptr;
+    }
+
+    template<typename Owner, typename Property, typename... Other>
+    internal::meta_prop_node * properties(Property &&property, Other &&... other) {
+        static auto prop{std::move(property)};
+
+        static internal::meta_prop_node node{
+            properties<Owner>(std::forward<Other>(other)...),
+            []() -> meta_any {
+                return std::get<0>(prop);
+            },
+            []() -> meta_any {
+                return std::get<1>(prop);
+            },
+            []() {
+                static meta_prop meta{&node};
+                return &meta;
+            }
+        };
+
+        assert(!duplicate(meta_any{std::get<0>(prop)}, node.next));
+        return &node;
+    }
+
+    template<typename... Property>
+    meta_factory<Type> type(hashed_string name, Property &&... property) ENTT_NOEXCEPT {
+        static internal::meta_type_node node{
+            name,
+            internal::meta_info<>::type,
+            properties<Type>(std::forward<Property>(property)...),
+            std::is_void_v<Type>,
+            std::is_enum_v<Type>,
+            std::is_class_v<Type>,
+            std::is_pointer_v<Type>,
+            std::is_pointer_v<Type> && std::is_function_v<std::remove_pointer_t<Type>>,
+            std::is_member_object_pointer_v<Type>,
+            std::is_member_function_pointer_v<Type>,
+            std::is_member_pointer_v<Type>,
+            std::is_arithmetic_v<Type>,
+            std::is_compound_v<Type>,
+            &internal::destroy<Type>,
+            []() {
+                static meta_type meta{&node};
+                return &meta;
+            }
+        };
+
+        assert(!duplicate(name, node.next));
+        assert(!internal::meta_info<Type>::type);
+        internal::meta_info<Type>::type = &node;
+        internal::meta_info<>::type = &node;
+
+        return *this;
+    }
+
+    meta_factory() ENTT_NOEXCEPT = default;
+
+public:
+    /**
+     * @brief Assigns a meta base to a meta type.
+     *
+     * A reflected base class must be a real base class of the reflected type.
+     *
+     * @tparam Base Type of the base class to assign to the meta type.
+     * @return A meta factory for the parent type.
+     */
+    template<typename Base>
+    meta_factory & base() ENTT_NOEXCEPT {
+        static_assert(std::is_base_of_v<Base, Type>);
+        auto * const type = internal::meta_info<Type>::resolve();
+
+        static internal::meta_base_node node{
+            type->base,
+            type,
+            &internal::meta_info<Base>::resolve,
+            [](void *instance) -> void * {
+                return static_cast<Base *>(static_cast<Type *>(instance));
+            },
+            []() {
+                static meta_base meta{&node};
+                return &meta;
+            }
+        };
+
+        assert((!internal::meta_info<Type>::template base<Base>));
+        internal::meta_info<Type>::template base<Base> = &node;
+        type->base = &node;
+
+        return *this;
+    }
+
+    /**
+     * @brief Assigns a meta conversion function to a meta type.
+     *
+     * The given type must be such that an instance of the reflected type can be
+     * converted to it.
+     *
+     * @tparam To Type of the conversion function to assign to the meta type.
+     * @return A meta factory for the parent type.
+     */
+    template<typename To>
+    meta_factory & conv() ENTT_NOEXCEPT {
+        static_assert(std::is_convertible_v<Type, std::decay_t<To>>);
+        auto * const type = internal::meta_info<Type>::resolve();
+
+        static internal::meta_conv_node node{
+            type->conv,
+            type,
+            &internal::meta_info<To>::resolve,
+            [](void *instance) -> meta_any {
+                return static_cast<std::decay_t<To>>(*static_cast<Type *>(instance));
+            },
+            []() {
+                static meta_conv meta{&node};
+                return &meta;
+            }
+        };
+
+        assert((!internal::meta_info<Type>::template conv<To>));
+        internal::meta_info<Type>::template conv<To> = &node;
+        type->conv = &node;
+
+        return *this;
+    }
+
+    /**
+     * @brief Assigns a meta constructor to a meta type.
+     *
+     * Free functions can be assigned to meta types in the role of
+     * constructors. All that is required is that they return an instance of the
+     * underlying type.<br/>
+     * From a client's point of view, nothing changes if a constructor of a meta
+     * type is a built-in one or a free function.
+     *
+     * @tparam Func The actual function to use as a constructor.
+     * @tparam Property Types of properties to assign to the meta data.
+     * @param property Properties to assign to the meta data.
+     * @return A meta factory for the parent type.
+     */
+    template<auto Func, typename... Property>
+    meta_factory & ctor(Property &&... property) ENTT_NOEXCEPT {
+        using helper_type = internal::meta_function_helper<std::integral_constant<decltype(Func), Func>>;
+        static_assert(std::is_same_v<typename helper_type::return_type, Type>);
+        auto * const type = internal::meta_info<Type>::resolve();
+
+        static internal::meta_ctor_node node{
+            type->ctor,
+            type,
+            properties<typename helper_type::args_type>(std::forward<Property>(property)...),
+            helper_type::size,
+            &helper_type::arg,
+            [](meta_any * const any) {
+                return internal::invoke<Type, Func>(nullptr, any, std::make_index_sequence<helper_type::size>{});
+            },
+            []() {
+                static meta_ctor meta{&node};
+                return &meta;
+            }
+        };
+
+        assert((!internal::meta_info<Type>::template ctor<typename helper_type::args_type>));
+        internal::meta_info<Type>::template ctor<typename helper_type::args_type> = &node;
+        type->ctor = &node;
+
+        return *this;
+    }
+
+    /**
+     * @brief Assigns a meta constructor to a meta type.
+     *
+     * A meta constructor is uniquely identified by the types of its arguments
+     * and is such that there exists an actual constructor of the underlying
+     * type that can be invoked with parameters whose types are those given.
+     *
+     * @tparam Args Types of arguments to use to construct an instance.
+     * @tparam Property Types of properties to assign to the meta data.
+     * @param property Properties to assign to the meta data.
+     * @return A meta factory for the parent type.
+     */
+    template<typename... Args, typename... Property>
+    meta_factory & ctor(Property &&... property) ENTT_NOEXCEPT {
+        using helper_type = internal::meta_function_helper<Type(Args...)>;
+        auto * const type = internal::meta_info<Type>::resolve();
+
+        static internal::meta_ctor_node node{
+            type->ctor,
+            type,
+            properties<typename helper_type::args_type>(std::forward<Property>(property)...),
+            helper_type::size,
+            &helper_type::arg,
+            [](meta_any * const any) {
+                return internal::construct<Type, Args...>(any, std::make_index_sequence<helper_type::size>{});
+            },
+            []() {
+                static meta_ctor meta{&node};
+                return &meta;
+            }
+        };
+
+        assert((!internal::meta_info<Type>::template ctor<typename helper_type::args_type>));
+        internal::meta_info<Type>::template ctor<typename helper_type::args_type> = &node;
+        type->ctor = &node;
+
+        return *this;
+    }
+
+    /**
+     * @brief Assigns a meta destructor to a meta type.
+     *
+     * Free functions can be assigned to meta types in the role of destructors.
+     * The signature of the function should identical to the following:
+     *
+     * @code{.cpp}
+     * void(Type &);
+     * @endcode
+     *
+     * From a client's point of view, nothing changes if the destructor of a
+     * meta type is the default one or a custom one.
+     *
+     * @tparam Func The actual function to use as a destructor.
+     * @return A meta factory for the parent type.
+     */
+    template<auto *Func>
+    std::enable_if_t<std::is_invocable_v<decltype(Func), Type &>, meta_factory &>
+    dtor() ENTT_NOEXCEPT {
+        auto * const type = internal::meta_info<Type>::resolve();
+
+        static internal::meta_dtor_node node{
+            type,
+            [](meta_handle handle) {
+                return handle.type() == internal::meta_info<Type>::resolve()->meta()
+                        ? ((*Func)(*static_cast<Type *>(handle.data())), true)
+                        : false;
+            },
+            []() {
+                static meta_dtor meta{&node};
+                return &meta;
+            }
+        };
+
+        assert(!internal::meta_info<Type>::type->dtor);
+        assert((!internal::meta_info<Type>::template dtor<Func>));
+        internal::meta_info<Type>::template dtor<Func> = &node;
+        internal::meta_info<Type>::type->dtor = &node;
+
+        return *this;
+    }
+
+    /**
+     * @brief Assigns a meta data to a meta type.
+     *
+     * Both data members and static or global variables can be assigned to a
+     * meta type.<br/>
+     * From a client's point of view, all the variables associated with the
+     * reflected object will appear as if they were part of the type.
+     *
+     * @tparam Data The actual variable to attach to the meta type.
+     * @tparam Property Types of properties to assign to the meta data.
+     * @param str The name to assign to the meta data.
+     * @param property Properties to assign to the meta data.
+     * @return A meta factory for the parent type.
+     */
+    template<auto Data, typename... Property>
+    meta_factory & data(const char *str, Property &&... property) ENTT_NOEXCEPT {
+        auto * const type = internal::meta_info<Type>::resolve();
+
+        static internal::meta_data_node node{
+            hashed_string{str},
+            type->data,
+            type,
+            properties<std::integral_constant<decltype(Data), Data>>(std::forward<Property>(property)...),
+            std::is_const_v<data_type<Data>>,
+            !std::is_member_object_pointer_v<decltype(Data)>,
+            &internal::meta_info<data_type<Data>>::resolve,
+            &internal::setter<std::is_const_v<data_type<Data>>, Type, Data>,
+            &internal::getter<Type, Data>,
+            []() {
+                static meta_data meta{&node};
+                return &meta;
+            }
+        };
+
+        assert(!duplicate(hashed_string{str}, node.next));
+        assert((!internal::meta_info<Type>::template data<Data>));
+        internal::meta_info<Type>::template data<Data> = &node;
+        type->data = &node;
+
+        return *this;
+    }
+
+    /**
+     * @brief Assigns a meta funcion to a meta type.
+     *
+     * Both member functions and free functions can be assigned to a meta
+     * type.<br/>
+     * From a client's point of view, all the functions associated with the
+     * reflected object will appear as if they were part of the type.
+     *
+     * @tparam Func The actual function to attach to the meta type.
+     * @tparam Property Types of properties to assign to the meta function.
+     * @param str The name to assign to the meta function.
+     * @param property Properties to assign to the meta function.
+     * @return A meta factory for the parent type.
+     */
+    template<auto Func, typename... Property>
+    meta_factory & func(const char *str, Property &&... property) ENTT_NOEXCEPT {
+        auto * const type = internal::meta_info<Type>::resolve();
+
+        static internal::meta_func_node node{
+            hashed_string{str},
+            type->func,
+            type,
+            properties<std::integral_constant<decltype(Func), Func>>(std::forward<Property>(property)...),
+            func_type<Func>::size,
+            func_type<Func>::is_const,
+            func_type<Func>::is_static,
+            &internal::meta_info<typename func_type<Func>::return_type>::resolve,
+            &func_type<Func>::arg,
+            [](meta_handle handle, meta_any *any) {
+                return internal::invoke<Type, Func>(handle, any, std::make_index_sequence<func_type<Func>::size>{});
+            },
+            []() {
+                static meta_func meta{&node};
+                return &meta;
+            }
+        };
+
+        assert(!duplicate(hashed_string{str}, node.next));
+        assert((!internal::meta_info<Type>::template func<Func>));
+        internal::meta_info<Type>::template func<Func> = &node;
+        type->func = &node;
+
+        return *this;
+    }
+
+    template<typename Other, typename... Property>
+    friend meta_factory<Other> reflect(const char *str, Property &&... property) ENTT_NOEXCEPT;
+};
+
+
+/**
+ * @brief Basic function to use for reflection.
+ *
+ * This is the point from which everything starts.<br/>
+ * By invoking this function with a type that is not yet reflected, a meta type
+ * is created to which it will be possible to attach data and functions through
+ * a dedicated factory.
+ *
+ * @tparam Type Type to reflect.
+ * @tparam Property Types of properties to assign to the reflected type.
+ * @param str The name to assign to the reflected type.
+ * @param property Properties to assign to the reflected type.
+ * @return A meta factory for the given type.
+ */
+template<typename Type, typename... Property>
+inline meta_factory<Type> reflect(const char *str, Property &&... property) ENTT_NOEXCEPT {
+    return meta_factory<Type>{}.type(hashed_string{str}, std::forward<Property>(property)...);
+}
+
+
+/**
+ * @brief Basic function to use for reflection.
+ *
+ * This is the point from which everything starts.<br/>
+ * By invoking this function with a type that is not yet reflected, a meta type
+ * is created to which it will be possible to attach data and functions through
+ * a dedicated factory.
+ *
+ * @tparam Type Type to reflect.
+ * @return A meta factory for the given type.
+ */
+template<typename Type>
+inline meta_factory<Type> reflect() ENTT_NOEXCEPT {
+    return meta_factory<Type>{};
+}
+
+
+/**
+ * @brief Returns the meta type associated with a given type.
+ * @tparam Type Type to use to search for a meta type.
+ * @return The meta type associated with the given type, if any.
+ */
+template<typename Type>
+inline meta_type * resolve() ENTT_NOEXCEPT {
+    return internal::meta_info<Type>::resolve()->meta();
+}
+
+
+/**
+ * @brief Returns the meta type associated with a given name.
+ * @param str The name to use to search for a meta type.
+ * @return The meta type associated with the given name, if any.
+ */
+inline meta_type * resolve(const char *str) ENTT_NOEXCEPT {
+    return internal::find_if([name = hashed_string{str}](auto *node) {
+        return node->name == name;
+    }, internal::meta_info<>::type);
+}
+
+
+/**
+ * @brief Iterates all the reflected types.
+ * @tparam Op Type of the function object to invoke.
+ * @param op A valid function object.
+ */
+template<typename Op>
+void resolve(Op op) ENTT_NOEXCEPT {
+    internal::iterate([op = std::move(op)](auto *node) {
+        op(node->meta());
+    }, internal::meta_info<>::type);
+}
+
+
+}
+
+
+#endif // ENTT_META_FACTORY_HPP

+ 1894 - 0
src/entt/meta/meta.hpp

@@ -0,0 +1,1894 @@
+#ifndef ENTT_META_META_HPP
+#define ENTT_META_META_HPP
+
+
+#include <tuple>
+#include <array>
+#include <memory>
+#include <cstring>
+#include <cassert>
+#include <cstddef>
+#include <utility>
+#include <type_traits>
+#include "../core/hashed_string.hpp"
+
+
+namespace entt {
+
+
+class meta_any;
+class meta_handle;
+class meta_prop;
+class meta_base;
+class meta_conv;
+class meta_ctor;
+class meta_dtor;
+class meta_data;
+class meta_func;
+class meta_type;
+
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+
+namespace internal {
+
+
+struct meta_prop_node;
+struct meta_base_node;
+struct meta_conv_node;
+struct meta_ctor_node;
+struct meta_dtor_node;
+struct meta_data_node;
+struct meta_func_node;
+struct meta_type_node;
+
+
+template<typename...>
+struct meta_node {
+    static meta_type_node *type;
+};
+
+
+template<typename... Type>
+meta_type_node * meta_node<Type...>::type = nullptr;
+
+
+template<typename Type>
+struct meta_node<Type> {
+    static meta_type_node *type;
+
+    template<typename>
+    static meta_base_node *base;
+
+    template<typename>
+    static meta_conv_node *conv;
+
+    template<typename>
+    static meta_ctor_node *ctor;
+
+    template<auto>
+    static meta_dtor_node *dtor;
+
+    template<auto>
+    static meta_data_node *data;
+
+    template<auto>
+    static meta_func_node *func;
+
+    static meta_type_node * resolve() ENTT_NOEXCEPT;
+};
+
+
+template<typename Type>
+meta_type_node * meta_node<Type>::type = nullptr;
+
+
+template<typename Type>
+template<typename>
+meta_base_node * meta_node<Type>::base = nullptr;
+
+
+template<typename Type>
+template<typename>
+meta_conv_node * meta_node<Type>::conv = nullptr;
+
+
+template<typename Type>
+template<typename>
+meta_ctor_node * meta_node<Type>::ctor = nullptr;
+
+
+template<typename Type>
+template<auto>
+meta_dtor_node * meta_node<Type>::dtor = nullptr;
+
+
+template<typename Type>
+template<auto>
+meta_data_node * meta_node<Type>::data = nullptr;
+
+
+template<typename Type>
+template<auto>
+meta_func_node * meta_node<Type>::func = nullptr;
+
+
+template<typename... Type>
+struct meta_info: meta_node<std::remove_cv_t<std::remove_reference_t<Type>>...> {};
+
+
+struct meta_prop_node final {
+    meta_prop_node * const next;
+    meta_any(* const key)();
+    meta_any(* const value)();
+    meta_prop *(* const meta)();
+};
+
+
+struct meta_base_node final {
+    meta_base_node * const next;
+    meta_type_node * const parent;
+    meta_type_node *(* const type)();
+    void *(* const cast)(void *);
+    meta_base *(* const meta)();
+};
+
+
+struct meta_conv_node final {
+    meta_conv_node * const next;
+    meta_type_node * const parent;
+    meta_type_node *(* const type)();
+    meta_any(* const conv)(void *);
+    meta_conv *(* const meta)();
+};
+
+
+struct meta_ctor_node final {
+    using size_type = std::size_t;
+    meta_ctor_node * const next;
+    meta_type_node * const parent;
+    meta_prop_node * const prop;
+    const size_type size;
+    meta_type_node *(* const arg)(size_type);
+    meta_any(* const invoke)(meta_any * const);
+    meta_ctor *(* const meta)();
+};
+
+
+struct meta_dtor_node final {
+    meta_type_node * const parent;
+    bool(* const invoke)(meta_handle);
+    meta_dtor *(* const meta)();
+};
+
+
+struct meta_data_node final {
+    const hashed_string name;
+    meta_data_node * const next;
+    meta_type_node * const parent;
+    meta_prop_node * const prop;
+    const bool is_const;
+    const bool is_static;
+    meta_type_node *(* const type)();
+    bool(* const set)(meta_handle, meta_any &);
+    meta_any(* const get)(meta_handle);
+    meta_data *(* const meta)();
+};
+
+
+struct meta_func_node final {
+    using size_type = std::size_t;
+    const hashed_string name;
+    meta_func_node * const next;
+    meta_type_node * const parent;
+    meta_prop_node * const prop;
+    const size_type size;
+    const bool is_const;
+    const bool is_static;
+    meta_type_node *(* const ret)();
+    meta_type_node *(* const arg)(size_type);
+    meta_any(* const invoke)(meta_handle, meta_any *);
+    meta_func *(* const meta)();
+};
+
+
+struct meta_type_node final {
+    const hashed_string name;
+    meta_type_node * const next;
+    meta_prop_node * const prop;
+    const bool is_void;
+    const bool is_enum;
+    const bool is_class;
+    const bool is_pointer;
+    const bool is_function_pointer;
+    const bool is_member_object_pointer;
+    const bool is_member_function_pointer;
+    const bool is_member_pointer;
+    const bool is_arithmetic;
+    const bool is_compound;
+    bool(* const destroy)(meta_handle);
+    meta_type *(* const meta)();
+    meta_base_node *base;
+    meta_conv_node *conv;
+    meta_ctor_node *ctor;
+    meta_dtor_node *dtor;
+    meta_data_node *data;
+    meta_func_node *func;
+};
+
+
+template<typename Op, typename Node>
+void iterate(Op op, const Node *curr) ENTT_NOEXCEPT {
+    while(curr) {
+        op(curr);
+        curr = curr->next;
+    }
+}
+
+
+template<auto Member, typename Op>
+void iterate(Op op, const meta_type_node *node) ENTT_NOEXCEPT {
+    if(node) {
+        auto *curr = node->base;
+        iterate(op, node->*Member);
+
+        while(curr) {
+            iterate<Member>(op, curr->type());
+            curr = curr->next;
+        }
+    }
+}
+
+
+template<typename Op, typename Node>
+auto find_if(Op op, const Node *curr) ENTT_NOEXCEPT {
+    while(curr && !op(curr)) {
+        curr = curr->next;
+    }
+
+    return curr ? curr->meta() : nullptr;
+}
+
+
+template<auto Member, typename Op>
+auto find_if(Op op, const meta_type_node *node) ENTT_NOEXCEPT
+-> decltype(find_if(op, node->*Member))
+{
+    decltype(find_if(op, node->*Member)) ret = nullptr;
+
+    if(node) {
+        ret = find_if(op, node->*Member);
+        auto *curr = node->base;
+
+        while(curr && !ret) {
+            ret = find_if<Member>(op, curr->type());
+            curr = curr->next;
+        }
+    }
+
+    return ret;
+}
+
+
+template<typename Type>
+const Type * try_cast(const meta_type_node *node, void *instance) ENTT_NOEXCEPT {
+    const auto *type = meta_info<Type>::resolve();
+    void *ret = nullptr;
+
+    if(node == type) {
+        ret = instance;
+    } else {
+        const auto *base = find_if<&meta_type_node::base>([type](auto *node) {
+            return node->type() == type;
+        }, node);
+
+        ret = base ? base->cast(instance) : nullptr;
+    }
+
+    return static_cast<const Type *>(ret);
+}
+
+
+template<auto Member>
+inline bool can_cast_or_convert(const meta_type_node *from, const meta_type_node *to) ENTT_NOEXCEPT {
+    return (from == to) || find_if<Member>([to](auto *node) {
+        return node->type() == to;
+    }, from);
+}
+
+
+}
+
+
+/**
+ * Internal details not to be documented.
+ * @endcond TURN_OFF_DOXYGEN
+ */
+
+
+/**
+ * @brief Meta any object.
+ *
+ * A meta any is an opaque container for single values of any type.
+ *
+ * This class uses a technique called small buffer optimization (SBO) to
+ * completely eliminate the need to allocate memory, where possible.<br/>
+ * From the user's point of view, nothing will change, but the elimination of
+ * allocations will reduce the jumps in memory and therefore will avoid chasing
+ * of pointers. This will greatly improve the use of the cache, thus increasing
+ * the overall performance.
+ */
+class meta_any final {
+    /*! @brief A meta handle is allowed to _inherit_ from a meta any. */
+    friend class meta_handle;
+
+    using storage_type = std::aligned_storage_t<sizeof(void *), alignof(void *)>;
+    using compare_fn_type = bool(*)(const void *, const void *);
+    using copy_fn_type = void *(*)(storage_type &, const void *);
+    using destroy_fn_type = void(*)(storage_type &);
+
+    template<typename Type>
+    inline static auto compare(int, const Type &lhs, const Type &rhs)
+    -> decltype(lhs == rhs, bool{})
+    {
+        return lhs == rhs;
+    }
+
+    template<typename Type>
+    inline static bool compare(char, const Type &lhs, const Type &rhs) {
+        return &lhs == &rhs;
+    }
+
+public:
+    /*! @brief Default constructor. */
+    meta_any() ENTT_NOEXCEPT
+        : storage{},
+          instance{nullptr},
+          destroy{nullptr},
+          node{nullptr},
+          comparator{nullptr}
+    {}
+
+    /**
+     * @brief Constructs a meta any from a given value.
+     *
+     * This class uses a technique called small buffer optimization (SBO) to
+     * completely eliminate the need to allocate memory, where possible.<br/>
+     * From the user's point of view, nothing will change, but the elimination
+     * of allocations will reduce the jumps in memory and therefore will avoid
+     * chasing of pointers. This will greatly improve the use of the cache, thus
+     * increasing the overall performance.
+     *
+     * @tparam Type Type of object to use to initialize the container.
+     * @param type An instance of an object to use to initialize the container.
+     */
+    template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, meta_any>>>
+    meta_any(Type &&type) {
+        using actual_type = std::decay_t<Type>;
+        node = internal::meta_info<Type>::resolve();
+
+        comparator = [](const void *lhs, const void *rhs) {
+            return compare(0, *static_cast<const actual_type *>(lhs), *static_cast<const actual_type *>(rhs));
+        };
+
+        if constexpr(sizeof(actual_type) <= sizeof(void *)) {
+            new (&storage) actual_type{std::forward<Type>(type)};
+            instance = &storage;
+
+            copy = [](storage_type &storage, const void *instance) -> void * {
+                return new (&storage) actual_type{*static_cast<const actual_type *>(instance)};
+            };
+
+            destroy = [](storage_type &storage) {
+                auto *instance = reinterpret_cast<actual_type *>(&storage);
+                internal::meta_info<Type>::resolve()->meta()->destroy(*instance);
+            };
+        } else {
+            using chunk_type = std::aligned_storage_t<sizeof(actual_type), alignof(actual_type)>;
+            auto *chunk = new chunk_type;
+            instance = new (chunk) actual_type{std::forward<Type>(type)};
+            new (&storage) chunk_type *{chunk};
+
+            copy = [](storage_type &storage, const void *instance) -> void * {
+                auto *chunk = new chunk_type;
+                new (&storage) chunk_type *{chunk};
+                return new (chunk) actual_type{*static_cast<const actual_type *>(instance)};
+            };
+
+            destroy = [](storage_type &storage) {
+                auto *chunk = *reinterpret_cast<chunk_type **>(&storage);
+                internal::meta_info<Type>::resolve()->meta()->destroy(*reinterpret_cast<actual_type *>(chunk));
+                delete chunk;
+            };
+        }
+    }
+
+    /**
+     * @brief Copy constructor.
+     * @param other The instance to copy from.
+     */
+    meta_any(const meta_any &other)
+        : meta_any{}
+    {
+        if(other) {
+            instance = other.copy(storage, other.instance);
+            destroy = other.destroy;
+            node = other.node;
+            comparator = other.comparator;
+            copy = other.copy;
+        }
+    }
+
+    /**
+     * @brief Move constructor.
+     *
+     * After meta any move construction, instances that have been moved from
+     * are placed in a valid but unspecified state. It's highly discouraged to
+     * continue using them.
+     *
+     * @param other The instance to move from.
+     */
+    meta_any(meta_any &&other) ENTT_NOEXCEPT
+        : meta_any{}
+    {
+        swap(*this, other);
+    }
+
+    /*! @brief Frees the internal storage, whatever it means. */
+    ~meta_any() {
+        if(destroy) {
+            destroy(storage);
+        }
+    }
+
+    /**
+     * @brief Assignment operator.
+     * @param other The instance to assign.
+     * @return This meta any object.
+     */
+    meta_any & operator=(meta_any other) {
+        swap(*this, other);
+        return *this;
+    }
+
+    /**
+     * @brief Returns the meta type of the underlying object.
+     * @return The meta type of the underlying object, if any.
+     */
+    inline meta_type * type() const ENTT_NOEXCEPT {
+        return node ? node->meta() : nullptr;
+    }
+
+    /**
+     * @brief Returns an opaque pointer to the contained instance.
+     * @return An opaque pointer the contained instance, if any.
+     */
+    inline const void * data() const ENTT_NOEXCEPT {
+        return instance;
+    }
+
+    /**
+     * @brief Returns an opaque pointer to the contained instance.
+     * @return An opaque pointer the contained instance, if any.
+     */
+    inline void * data() ENTT_NOEXCEPT {
+        return const_cast<void *>(std::as_const(*this).data());
+    }
+
+    /**
+     * @brief Checks if it's possible to cast an instance to a given type.
+     * @tparam Type Type to which to cast the instance.
+     * @return True if the cast is viable, false otherwise.
+     */
+    template<typename Type>
+    inline bool can_cast() const ENTT_NOEXCEPT {
+        const auto *type = internal::meta_info<Type>::resolve();
+        return internal::can_cast_or_convert<&internal::meta_type_node::base>(node, type);
+    }
+
+    /**
+     * @brief Tries to cast an instance to a given type.
+     *
+     * The type of the instance must be such that the cast is possible.
+     *
+     * @warning
+     * Attempting to perform a cast that isn't viable results in undefined
+     * behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case
+     * the cast is not feasible.
+     *
+     * @tparam Type Type to which to cast the instance.
+     * @return A reference to the contained instance.
+     */
+    template<typename Type>
+    inline const Type & cast() const ENTT_NOEXCEPT {
+        assert(can_cast<Type>());
+        return *internal::try_cast<Type>(node, instance);
+    }
+
+    /**
+     * @brief Tries to cast an instance to a given type.
+     *
+     * The type of the instance must be such that the cast is possible.
+     *
+     * @warning
+     * Attempting to perform a cast that isn't viable results in undefined
+     * behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case
+     * the cast is not feasible.
+     *
+     * @tparam Type Type to which to cast the instance.
+     * @return A reference to the contained instance.
+     */
+    template<typename Type>
+    inline Type & cast() ENTT_NOEXCEPT {
+        return const_cast<Type &>(std::as_const(*this).cast<Type>());
+    }
+
+    /**
+     * @brief Checks if it's possible to convert an instance to a given type.
+     * @tparam Type Type to which to convert the instance.
+     * @return True if the conversion is viable, false otherwise.
+     */
+    template<typename Type>
+    inline bool can_convert() const ENTT_NOEXCEPT {
+        const auto *type = internal::meta_info<Type>::resolve();
+        return internal::can_cast_or_convert<&internal::meta_type_node::conv>(node, type);
+    }
+
+    /**
+     * @brief Tries to convert an instance to a given type and returns it.
+     * @tparam Type Type to which to convert the instance.
+     * @return A valid meta any object if the conversion is possible, an invalid
+     * one otherwise.
+     */
+    template<typename Type>
+    inline meta_any convert() const ENTT_NOEXCEPT {
+        const auto *type = internal::meta_info<Type>::resolve();
+        meta_any any{};
+
+        if(node == type) {
+            any = *static_cast<const Type *>(instance);
+        } else {
+            const auto *conv = internal::find_if<&internal::meta_type_node::conv>([type](auto *node) {
+                return node->type() == type;
+            }, node);
+
+            if(conv) {
+                any = conv->convert(instance);
+            }
+        }
+
+        return any;
+    }
+
+    /**
+     * @brief Tries to convert an instance to a given type.
+     * @tparam Type Type to which to convert the instance.
+     * @return True if the conversion is possible, false otherwise.
+     */
+    template<typename Type>
+    inline bool convert() ENTT_NOEXCEPT {
+        bool valid = (node == internal::meta_info<Type>::resolve());
+
+        if(!valid) {
+            auto any = std::as_const(*this).convert<Type>();
+
+            if(any) {
+                std::swap(*this, any);
+                valid = true;
+            }
+        }
+
+        return valid;
+    }
+
+    /**
+     * @brief Returns false if a container is empty, true otherwise.
+     * @return False if the container is empty, true otherwise.
+     */
+    inline explicit operator bool() const ENTT_NOEXCEPT {
+        return destroy;
+    }
+
+    /**
+     * @brief Checks if two containers differ in their content.
+     * @param other Container with which to compare.
+     * @return False if the two containers differ in their content, true
+     * otherwise.
+     */
+    inline bool operator==(const meta_any &other) const ENTT_NOEXCEPT {
+        return (!instance && !other.instance) || (instance && other.instance && node == other.node && comparator(instance, other.instance));
+    }
+
+    /**
+     * @brief Swaps two meta any objects.
+     * @param lhs A valid meta any object.
+     * @param rhs A valid meta any object.
+     */
+    friend void swap(meta_any &lhs, meta_any &rhs) {
+        using std::swap;
+
+        std::swap(lhs.storage, rhs.storage);
+        std::swap(lhs.instance, rhs.instance);
+        std::swap(lhs.destroy, rhs.destroy);
+        std::swap(lhs.node, rhs.node);
+        std::swap(lhs.comparator, rhs.comparator);
+        std::swap(lhs.copy, rhs.copy);
+
+        if(lhs.instance == &rhs.storage) {
+            lhs.instance = &lhs.storage;
+        }
+
+        if(rhs.instance == &lhs.storage) {
+            rhs.instance = &rhs.storage;
+        }
+    }
+
+private:
+    storage_type storage;
+    void *instance;
+    destroy_fn_type destroy;
+    internal::meta_type_node *node;
+    compare_fn_type comparator;
+    copy_fn_type copy;
+};
+
+
+/**
+ * @brief Meta handle object.
+ *
+ * A meta handle is an opaque pointer to an instance of any type.
+ *
+ * A handle doesn't perform copies and isn't responsible for the contained
+ * object. It doesn't prolong the lifetime of the pointed instance. Users are
+ * responsible for ensuring that the target object remains alive for the entire
+ * interval of use of the handle.
+ */
+class meta_handle final {
+    meta_handle(int, meta_any &any) ENTT_NOEXCEPT
+        : node{any.node},
+          instance{any.instance}
+    {}
+
+    template<typename Type>
+    meta_handle(char, Type &&instance) ENTT_NOEXCEPT
+        : node{internal::meta_info<Type>::resolve()},
+          instance{&instance}
+    {}
+
+public:
+    /*! @brief Default constructor. */
+    meta_handle() ENTT_NOEXCEPT
+        : node{nullptr},
+          instance{nullptr}
+    {}
+
+    /**
+     * @brief Constructs a meta handle from a given instance.
+     * @tparam Type Type of object to use to initialize the handle.
+     * @param instance A reference to an object to use to initialize the handle.
+     */
+    template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, meta_handle>>>
+    meta_handle(Type &&instance) ENTT_NOEXCEPT
+        : meta_handle{0, std::forward<Type>(instance)}
+    {}
+
+    /*! @brief Default destructor. */
+    ~meta_handle() ENTT_NOEXCEPT = default;
+
+    /*! @brief Default copy constructor. */
+    meta_handle(const meta_handle &) ENTT_NOEXCEPT = default;
+
+    /*! @brief Default move constructor. */
+    meta_handle(meta_handle &&) ENTT_NOEXCEPT = default;
+
+    /*! @brief Default copy assignment operator. @return This handle. */
+    meta_handle & operator=(const meta_handle &) ENTT_NOEXCEPT = default;
+
+    /*! @brief Default move assignment operator. @return This handle. */
+    meta_handle & operator=(meta_handle &&) ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Returns the meta type of the underlying object.
+     * @return The meta type of the underlying object, if any.
+     */
+    inline meta_type * type() const ENTT_NOEXCEPT {
+        return node ? node->meta() : nullptr;
+    }
+
+    /**
+     * @brief Tries to cast an instance to a given type.
+     *
+     * The type of the instance must be such that the conversion is possible.
+     *
+     * @warning
+     * Attempting to perform a conversion that isn't viable results in undefined
+     * behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case
+     * the conversion is not feasible.
+     *
+     * @tparam Type Type to which to cast the instance.
+     * @return A pointer to the contained instance.
+     */
+    template<typename Type>
+    inline const Type * try_cast() const ENTT_NOEXCEPT {
+        return internal::try_cast<Type>(node, instance);
+    }
+
+    /**
+     * @brief Tries to cast an instance to a given type.
+     *
+     * The type of the instance must be such that the conversion is possible.
+     *
+     * @warning
+     * Attempting to perform a conversion that isn't viable results in undefined
+     * behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case
+     * the conversion is not feasible.
+     *
+     * @tparam Type Type to which to cast the instance.
+     * @return A pointer to the contained instance.
+     */
+    template<typename Type>
+    inline Type * try_cast() ENTT_NOEXCEPT {
+        return const_cast<Type *>(std::as_const(*this).try_cast<Type>());
+    }
+
+    /**
+     * @brief Returns an opaque pointer to the contained instance.
+     * @return An opaque pointer the contained instance, if any.
+     */
+    inline const void * data() const ENTT_NOEXCEPT {
+        return instance;
+    }
+
+    /**
+     * @brief Returns an opaque pointer to the contained instance.
+     * @return An opaque pointer the contained instance, if any.
+     */
+    inline void * data() ENTT_NOEXCEPT {
+        return const_cast<void *>(std::as_const(*this).data());
+    }
+
+    /**
+     * @brief Returns false if a handle is empty, true otherwise.
+     * @return False if the handle is empty, true otherwise.
+     */
+    inline explicit operator bool() const ENTT_NOEXCEPT {
+        return instance;
+    }
+
+private:
+    const internal::meta_type_node *node;
+    void *instance;
+};
+
+
+/**
+ * @brief Checks if two containers differ in their content.
+ * @param lhs A meta any object, either empty or not.
+ * @param rhs A meta any object, either empty or not.
+ * @return True if the two containers differ in their content, false otherwise.
+ */
+inline bool operator!=(const meta_any &lhs, const meta_any &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+
+/**
+ * @brief Meta property object.
+ *
+ * A meta property is an opaque container for a key/value pair.<br/>
+ * Properties are associated with any other meta object to enrich it.
+ */
+class meta_prop final {
+    /*! @brief A factory is allowed to create meta objects. */
+    template<typename>
+    friend class meta_factory;
+
+    meta_prop(internal::meta_prop_node * node) ENTT_NOEXCEPT
+        : node{node}
+    {}
+
+public:
+    /**
+     * @brief Returns the stored key.
+     * @return A meta any containing the key stored with the given property.
+     */
+    inline meta_any key() const ENTT_NOEXCEPT {
+        return node->key();
+    }
+
+    /**
+     * @brief Returns the stored value.
+     * @return A meta any containing the value stored with the given property.
+     */
+    inline meta_any value() const ENTT_NOEXCEPT {
+        return node->value();
+    }
+
+private:
+    internal::meta_prop_node *node;
+};
+
+
+/**
+ * @brief Meta base object.
+ *
+ * A meta base is an opaque container for a base class to be used to walk
+ * through hierarchies.
+ */
+class meta_base final {
+    /*! @brief A factory is allowed to create meta objects. */
+    template<typename>
+    friend class meta_factory;
+
+    meta_base(internal::meta_base_node * node) ENTT_NOEXCEPT
+        : node{node}
+    {}
+
+public:
+    /**
+     * @brief Returns the meta type to which a meta base belongs.
+     * @return The meta type to which the meta base belongs.
+     */
+    inline meta_type * parent() const ENTT_NOEXCEPT {
+        return node->parent->meta();
+    }
+
+    /**
+     * @brief Returns the meta type of a given meta base.
+     * @return The meta type of the meta base.
+     */
+    inline meta_type * type() const ENTT_NOEXCEPT {
+        return node->type()->meta();
+    }
+
+    /**
+     * @brief Casts an instance from a parent type to a base type.
+     * @param instance The instance to cast.
+     * @return An opaque pointer to the base type.
+     */
+    inline void * cast(void *instance) const ENTT_NOEXCEPT {
+        return node->cast(instance);
+    }
+
+private:
+    internal::meta_base_node *node;
+};
+
+
+/**
+ * @brief Meta conversion function object.
+ *
+ * A meta conversion function is an opaque container for a conversion function
+ * to be used to convert a given instance to another type.
+ */
+class meta_conv final {
+    /*! @brief A factory is allowed to create meta objects. */
+    template<typename>
+    friend class meta_factory;
+
+    meta_conv(internal::meta_conv_node * node) ENTT_NOEXCEPT
+        : node{node}
+    {}
+
+public:
+    /**
+     * @brief Returns the meta type to which a meta conversion function belongs.
+     * @return The meta type to which the meta conversion function belongs.
+     */
+    inline meta_type * parent() const ENTT_NOEXCEPT {
+        return node->parent->meta();
+    }
+
+    /**
+     * @brief Returns the meta type of a given meta conversion function.
+     * @return The meta type of the meta conversion function.
+     */
+    inline meta_type * type() const ENTT_NOEXCEPT {
+        return node->type()->meta();
+    }
+
+    /**
+     * @brief Converts an instance to a given type.
+     * @param instance The instance to convert.
+     * @return An opaque pointer to the instance to convert.
+     */
+    inline meta_any convert(void *instance) const ENTT_NOEXCEPT {
+        return node->conv(instance);
+    }
+
+private:
+    internal::meta_conv_node *node;
+};
+
+
+/**
+ * @brief Meta constructor object.
+ *
+ * A meta constructor is an opaque container for a function to be used to
+ * construct instances of a given type.
+ */
+class meta_ctor final {
+    /*! @brief A factory is allowed to create meta objects. */
+    template<typename>
+    friend class meta_factory;
+
+    meta_ctor(internal::meta_ctor_node * node) ENTT_NOEXCEPT
+        : node{node}
+    {}
+
+public:
+    /*! @brief Unsigned integer type. */
+    using size_type = typename internal::meta_ctor_node::size_type;
+
+    /**
+     * @brief Returns the meta type to which a meta constructor belongs.
+     * @return The meta type to which the meta constructor belongs.
+     */
+    inline meta_type * parent() const ENTT_NOEXCEPT {
+        return node->parent->meta();
+    }
+
+    /**
+     * @brief Returns the number of arguments accepted by a meta constructor.
+     * @return The number of arguments accepted by the meta constructor.
+     */
+    inline size_type size() const ENTT_NOEXCEPT {
+        return node->size;
+    }
+
+    /**
+     * @brief Returns the meta type of the i-th argument of a meta constructor.
+     * @param index The index of the argument of which to return the meta type.
+     * @return The meta type of the i-th argument of a meta constructor, if any.
+     */
+    inline meta_type * arg(size_type index) const ENTT_NOEXCEPT {
+        return index < size() ? node->arg(index)->meta() : nullptr;
+    }
+
+    /**
+     * @brief Creates an instance of the underlying type, if possible.
+     *
+     * To create a valid instance, the types of the parameters must coincide
+     * exactly with those required by the underlying meta constructor.
+     * Otherwise, an empty and then invalid container is returned.
+     *
+     * @tparam Args Types of arguments to use to construct the instance.
+     * @param args Parameters to use to construct the instance.
+     * @return A meta any containing the new instance, if any.
+     */
+    template<typename... Args>
+    meta_any invoke(Args &&... args) const {
+        std::array<meta_any, sizeof...(Args)> arguments{{std::forward<Args>(args)...}};
+        meta_any any{};
+
+        if(sizeof...(Args) == size()) {
+            any = node->invoke(arguments.data());
+        }
+
+        return any;
+    }
+
+    /**
+     * @brief Iterates all the properties assigned to a meta constructor.
+     * @tparam Op Type of the function object to invoke.
+     * @param op A valid function object.
+     */
+    template<typename Op>
+    inline std::enable_if_t<std::is_invocable_v<Op, meta_prop *>, void>
+    prop(Op op) const ENTT_NOEXCEPT {
+        internal::iterate([op = std::move(op)](auto *node) {
+            op(node->meta());
+        }, node->prop);
+    }
+
+    /**
+     * @brief Returns the property associated with a given key.
+     * @tparam Key Type of key to use to search for a property.
+     * @param key The key to use to search for a property.
+     * @return The property associated with the given key, if any.
+     */
+    template<typename Key>
+    inline std::enable_if_t<!std::is_invocable_v<Key, meta_prop *>, meta_prop *>
+    prop(Key &&key) const ENTT_NOEXCEPT {
+        return internal::find_if([key = meta_any{std::forward<Key>(key)}](auto *curr) {
+            return curr->key() == key;
+        }, node->prop);
+    }
+
+private:
+    internal::meta_ctor_node *node;
+};
+
+
+/**
+ * @brief Meta destructor object.
+ *
+ * A meta destructor is an opaque container for a function to be used to
+ * destroy instances of a given type.
+ */
+class meta_dtor final {
+    /*! @brief A factory is allowed to create meta objects. */
+    template<typename>
+    friend class meta_factory;
+
+    meta_dtor(internal::meta_dtor_node * node) ENTT_NOEXCEPT
+        : node{node}
+    {}
+
+public:
+    /**
+     * @brief Returns the meta type to which a meta destructor belongs.
+     * @return The meta type to which the meta destructor belongs.
+     */
+    inline meta_type * parent() const ENTT_NOEXCEPT {
+        return node->parent->meta();
+    }
+
+    /**
+     * @brief Destroys an instance of the underlying type.
+     *
+     * It must be possible to cast the instance to the parent type of the meta
+     * destructor. Otherwise, invoking the meta destructor results in an
+     * undefined behavior.
+     *
+     * @param handle An opaque pointer to an instance of the underlying type.
+     * @return True in case of success, false otherwise.
+     */
+    inline bool invoke(meta_handle handle) const {
+        return node->invoke(handle);
+    }
+
+private:
+    internal::meta_dtor_node *node;
+};
+
+
+/**
+ * @brief Meta data object.
+ *
+ * A meta data is an opaque container for a data member associated with a given
+ * type.
+ */
+class meta_data final {
+    /*! @brief A factory is allowed to create meta objects. */
+    template<typename>
+    friend class meta_factory;
+
+    meta_data(internal::meta_data_node * node) ENTT_NOEXCEPT
+        : node{node}
+    {}
+
+public:
+    /**
+     * @brief Returns the name assigned to a given meta data.
+     * @return The name assigned to the meta data.
+     */
+    inline const char * name() const ENTT_NOEXCEPT {
+        return node->name;
+    }
+
+    /**
+     * @brief Returns the meta type to which a meta data belongs.
+     * @return The meta type to which the meta data belongs.
+     */
+    inline meta_type * parent() const ENTT_NOEXCEPT {
+        return node->parent->meta();
+    }
+
+    /**
+     * @brief Indicates whether a given meta data is constant or not.
+     * @return True if the meta data is constant, false otherwise.
+     */
+    inline bool is_const() const ENTT_NOEXCEPT {
+        return node->is_const;
+    }
+
+    /**
+     * @brief Indicates whether a given meta data is static or not.
+     *
+     * A static meta data is such that it can be accessed using a null pointer
+     * as an instance.
+     *
+     * @return True if the meta data is static, false otherwise.
+     */
+    inline bool is_static() const ENTT_NOEXCEPT {
+        return node->is_static;
+    }
+
+    /**
+     * @brief Returns the meta type of a given meta data.
+     * @return The meta type of the meta data.
+     */
+    inline meta_type * type() const ENTT_NOEXCEPT {
+        return node->type()->meta();
+    }
+
+    /**
+     * @brief Sets the value of the variable enclosed by a given meta type.
+     *
+     * It must be possible to cast the instance to the parent type of the meta
+     * data. Otherwise, invoking the setter results in an undefined
+     * behavior.<br/>
+     * The type of the value must coincide exactly with that of the variable
+     * enclosed by the meta data. Otherwise, invoking the setter does nothing.
+     *
+     * @tparam Type Type of value to assign.
+     * @param handle An opaque pointer to an instance of the underlying type.
+     * @param value Parameter to use to set the underlying variable.
+     * @return True in case of success, false otherwise.
+     */
+    template<typename Type>
+    inline bool set(meta_handle handle, Type &&value) const {
+        meta_any any{std::forward<Type>(value)};
+        return node->set(handle, any);
+    }
+
+    /**
+     * @brief Gets the value of the variable enclosed by a given meta type.
+     *
+     * It must be possible to cast the instance to the parent type of the meta
+     * function. Otherwise, invoking the getter results in an undefined
+     * behavior.
+     *
+     * @param handle An opaque pointer to an instance of the underlying type.
+     * @return A meta any containing the value of the underlying variable.
+     */
+    inline meta_any get(meta_handle handle) const ENTT_NOEXCEPT {
+        return node->get(handle);
+    }
+
+    /**
+     * @brief Iterates all the properties assigned to a meta data.
+     * @tparam Op Type of the function object to invoke.
+     * @param op A valid function object.
+     */
+    template<typename Op>
+    inline std::enable_if_t<std::is_invocable_v<Op, meta_prop *>, void>
+    prop(Op op) const ENTT_NOEXCEPT {
+        internal::iterate([op = std::move(op)](auto *node) {
+            op(node->meta());
+        }, node->prop);
+    }
+
+    /**
+     * @brief Returns the property associated with a given key.
+     * @tparam Key Type of key to use to search for a property.
+     * @param key The key to use to search for a property.
+     * @return The property associated with the given key, if any.
+     */
+    template<typename Key>
+    inline std::enable_if_t<!std::is_invocable_v<Key, meta_prop *>, meta_prop *>
+    prop(Key &&key) const ENTT_NOEXCEPT {
+        return internal::find_if([key = meta_any{std::forward<Key>(key)}](auto *curr) {
+            return curr->key() == key;
+        }, node->prop);
+    }
+
+private:
+    internal::meta_data_node *node;
+};
+
+
+/**
+ * @brief Meta function object.
+ *
+ * A meta function is an opaque container for a member function associated with
+ * a given type.
+ */
+class meta_func final {
+    /*! @brief A factory is allowed to create meta objects. */
+    template<typename>
+    friend class meta_factory;
+
+    meta_func(internal::meta_func_node * node) ENTT_NOEXCEPT
+        : node{node}
+    {}
+
+public:
+    /*! @brief Unsigned integer type. */
+    using size_type = typename internal::meta_func_node::size_type;
+
+    /**
+     * @brief Returns the name assigned to a given meta function.
+     * @return The name assigned to the meta function.
+     */
+    inline const char * name() const ENTT_NOEXCEPT {
+        return node->name;
+    }
+
+    /**
+     * @brief Returns the meta type to which a meta function belongs.
+     * @return The meta type to which the meta function belongs.
+     */
+    inline meta_type * parent() const ENTT_NOEXCEPT {
+        return node->parent->meta();
+    }
+
+    /**
+     * @brief Returns the number of arguments accepted by a meta function.
+     * @return The number of arguments accepted by the meta function.
+     */
+    inline size_type size() const ENTT_NOEXCEPT {
+        return node->size;
+    }
+
+    /**
+     * @brief Indicates whether a given meta function is constant or not.
+     * @return True if the meta function is constant, false otherwise.
+     */
+    inline bool is_const() const ENTT_NOEXCEPT {
+        return node->is_const;
+    }
+
+    /**
+     * @brief Indicates whether a given meta function is static or not.
+     *
+     * A static meta function is such that it can be invoked using a null
+     * pointer as an instance.
+     *
+     * @return True if the meta function is static, false otherwise.
+     */
+    inline bool is_static() const ENTT_NOEXCEPT {
+        return node->is_static;
+    }
+
+    /**
+     * @brief Returns the meta type of the return type of a meta function.
+     * @return The meta type of the return type of the meta function.
+     */
+    inline meta_type * ret() const ENTT_NOEXCEPT {
+        return node->ret()->meta();
+    }
+
+    /**
+     * @brief Returns the meta type of the i-th argument of a meta function.
+     * @param index The index of the argument of which to return the meta type.
+     * @return The meta type of the i-th argument of a meta function, if any.
+     */
+    inline meta_type * arg(size_type index) const ENTT_NOEXCEPT {
+        return index < size() ? node->arg(index)->meta() : nullptr;
+    }
+
+    /**
+     * @brief Invokes the underlying function, if possible.
+     *
+     * To invoke a meta function, the types of the parameters must coincide
+     * exactly with those required by the underlying function. Otherwise, an
+     * empty and then invalid container is returned.<br/>
+     * It must be possible to cast the instance to the parent type of the meta
+     * function. Otherwise, invoking the underlying function results in an
+     * undefined behavior.
+     *
+     * @tparam Args Types of arguments to use to invoke the function.
+     * @param handle An opaque pointer to an instance of the underlying type.
+     * @param args Parameters to use to invoke the function.
+     * @return A meta any containing the returned value, if any.
+     */
+    template<typename... Args>
+    meta_any invoke(meta_handle handle, Args &&... args) const {
+        std::array<meta_any, sizeof...(Args)> arguments{{std::forward<Args>(args)...}};
+        meta_any any{};
+
+        if(sizeof...(Args) == size()) {
+            any = node->invoke(handle, arguments.data());
+        }
+
+        return any;
+    }
+
+    /**
+     * @brief Iterates all the properties assigned to a meta function.
+     * @tparam Op Type of the function object to invoke.
+     * @param op A valid function object.
+     */
+    template<typename Op>
+    inline std::enable_if_t<std::is_invocable_v<Op, meta_prop *>, void>
+    prop(Op op) const ENTT_NOEXCEPT {
+        internal::iterate([op = std::move(op)](auto *node) {
+            op(node->meta());
+        }, node->prop);
+    }
+
+    /**
+     * @brief Returns the property associated with a given key.
+     * @tparam Key Type of key to use to search for a property.
+     * @param key The key to use to search for a property.
+     * @return The property associated with the given key, if any.
+     */
+    template<typename Key>
+    inline std::enable_if_t<!std::is_invocable_v<Key, meta_prop *>, meta_prop *>
+    prop(Key &&key) const ENTT_NOEXCEPT {
+        return internal::find_if([key = meta_any{std::forward<Key>(key)}](auto *curr) {
+            return curr->key() == key;
+        }, node->prop);
+    }
+
+private:
+    internal::meta_func_node *node;
+};
+
+
+/**
+ * @brief Meta type object.
+ *
+ * A meta type is the starting point for accessing a reflected type, thus being
+ * able to work through it on real objects.
+ */
+class meta_type final {
+    /*! @brief A factory is allowed to create meta objects. */
+    template<typename>
+    friend class meta_factory;
+
+    /*! @brief A meta node is allowed to create meta objects. */
+    template<typename...>
+    friend struct internal::meta_node;
+
+    meta_type(internal::meta_type_node * node) ENTT_NOEXCEPT
+        : node{node}
+    {}
+
+    template<typename... Args, std::size_t... Indexes>
+    inline auto ctor(std::index_sequence<Indexes...>) const ENTT_NOEXCEPT {
+        return internal::find_if([](auto *node) {
+            return node->size == sizeof...(Args) &&
+                    (([](auto *from, auto *to) {
+                        return internal::can_cast_or_convert<&internal::meta_type_node::base>(from, to)
+                                || internal::can_cast_or_convert<&internal::meta_type_node::conv>(from, to);
+                    }(internal::meta_info<Args>::resolve(), node->arg(Indexes))) && ...);
+        }, node->ctor);
+    }
+
+public:
+    /**
+     * @brief Returns the name assigned to a given meta type.
+     * @return The name assigned to the meta type.
+     */
+    inline const char * name() const ENTT_NOEXCEPT {
+        return node->name;
+    }
+
+    /**
+     * @brief Indicates whether a given meta type refers to void or not.
+     * @return True if the underlying type is void, false otherwise.
+     */
+    inline bool is_void() const ENTT_NOEXCEPT {
+        return node->is_void;
+    }
+
+    /**
+     * @brief Indicates whether a given meta type refers to an enum or not.
+     * @return True if the underlying type is an enum, false otherwise.
+     */
+    inline bool is_enum() const ENTT_NOEXCEPT {
+        return node->is_enum;
+    }
+
+    /**
+     * @brief Indicates whether a given meta type refers to a class or not.
+     * @return True if the underlying type is a class, false otherwise.
+     */
+    inline bool is_class() const ENTT_NOEXCEPT {
+        return node->is_class;
+    }
+
+    /**
+     * @brief Indicates whether a given meta type refers to a pointer or not.
+     * @return True if the underlying type is a pointer, false otherwise.
+     */
+    inline bool is_pointer() const ENTT_NOEXCEPT {
+        return node->is_pointer;
+    }
+
+    /**
+     * @brief Indicates whether a given meta type refers to a function pointer
+     * or not.
+     * @return True if the underlying type is a function pointer, false
+     * otherwise.
+     */
+    inline bool is_function_pointer() const ENTT_NOEXCEPT {
+        return node->is_function_pointer;
+    }
+
+    /**
+     * @brief Indicates whether a given meta type refers to a pointer to data
+     * member or not.
+     * @return True if the underlying type is a pointer to data member, false
+     * otherwise.
+     */
+    inline bool is_member_object_pointer() const ENTT_NOEXCEPT {
+        return node->is_member_object_pointer;
+    }
+
+    /**
+     * @brief Indicates whether a given meta type refers to a pointer to member
+     * function or not.
+     * @return True if the underlying type is a pointer to member function,
+     * false otherwise.
+     */
+    inline bool is_member_function_pointer() const ENTT_NOEXCEPT {
+        return node->is_member_function_pointer;
+    }
+
+    /**
+     * @brief Indicates whether a given meta type refers to a pointer to member
+     * or not.
+     * @return True if the underlying type is a pointer to member, false
+     * otherwise.
+     */
+    inline bool is_member_pointer() const ENTT_NOEXCEPT {
+        return node->is_member_pointer;
+    }
+
+    /**
+     * @brief Indicates whether a given meta type refers to an arithmetic type
+     * or not.
+     * @return True if the underlying type is an arithmetic type, false
+     * otherwise.
+     */
+    inline bool is_arithmetic() const ENTT_NOEXCEPT {
+        return node->is_arithmetic;
+    }
+
+    /**
+     * @brief Indicates whether a given meta type refers to a compound type or
+     * not.
+     * @return True if the underlying type is a compound type, false otherwise.
+     */
+    inline bool is_compound() const ENTT_NOEXCEPT {
+        return node->is_compound;
+    }
+
+    /**
+     * @brief Iterates all the meta base of a meta type.
+     *
+     * Iteratively returns **all** the base classes of the given type.
+     *
+     * @tparam Op Type of the function object to invoke.
+     * @param op A valid function object.
+     */
+    template<typename Op>
+    inline void base(Op op) const ENTT_NOEXCEPT {
+        internal::iterate<&internal::meta_type_node::base>([op = std::move(op)](auto *node) {
+            op(node->meta());
+        }, node);
+    }
+
+    /**
+     * @brief Returns the meta base associated with a given name.
+     *
+     * Searches recursively among **all** the base classes of the given type.
+     *
+     * @param str The name to use to search for a meta base.
+     * @return The meta base associated with the given name, if any.
+     */
+    inline meta_base * base(const char *str) const ENTT_NOEXCEPT {
+        return internal::find_if<&internal::meta_type_node::base>([name = hashed_string{str}](auto *node) {
+            return node->type()->name == name;
+        }, node);
+    }
+
+    /**
+     * @brief Iterates all the meta conversion functions of a meta type.
+     *
+     * Iteratively returns **all** the meta conversion functions of the given
+     * type.
+     *
+     * @tparam Op Type of the function object to invoke.
+     * @param op A valid function object.
+     */
+    template<typename Op>
+    inline void conv(Op op) const ENTT_NOEXCEPT {
+        internal::iterate<&internal::meta_type_node::conv>([op = std::move(op)](auto *node) {
+            op(node->meta());
+        }, node);
+    }
+
+    /**
+     * @brief Returns the meta conversion function associated with a given type.
+     *
+     * Searches recursively among **all** the conversion functions of the given
+     * type.
+     *
+     * @tparam Type The type to use to search for a meta conversion function.
+     * @return The meta conversion function associated with the given type, if
+     * any.
+     */
+    template<typename Type>
+    inline meta_conv * conv() const ENTT_NOEXCEPT {
+        return internal::find_if<&internal::meta_type_node::conv>([type = internal::meta_info<Type>::resolve()](auto *node) {
+            return node->type() == type;
+        }, node);
+    }
+
+    /**
+     * @brief Iterates all the meta constructors of a meta type.
+     * @tparam Op Type of the function object to invoke.
+     * @param op A valid function object.
+     */
+    template<typename Op>
+    inline void ctor(Op op) const ENTT_NOEXCEPT {
+        internal::iterate([op = std::move(op)](auto *node) {
+            op(node->meta());
+        }, node->ctor);
+    }
+
+    /**
+     * @brief Returns the meta constructor that accepts a given list of types of
+     * arguments.
+     * @return The requested meta constructor, if any.
+     */
+    template<typename... Args>
+    inline meta_ctor * ctor() const ENTT_NOEXCEPT {
+        return ctor<Args...>(std::make_index_sequence<sizeof...(Args)>{});
+    }
+
+    /**
+     * @brief Returns the meta destructor associated with a given type.
+     * @return The meta destructor associated with the given type, if any.
+     */
+    inline meta_dtor * dtor() const ENTT_NOEXCEPT {
+        return node->dtor ? node->dtor->meta() : nullptr;
+    }
+
+    /**
+     * @brief Iterates all the meta data of a meta type.
+     *
+     * Iteratively returns **all** the meta data of the given type. This means
+     * that the meta data of the base classes will also be returned, if any.
+     *
+     * @tparam Op Type of the function object to invoke.
+     * @param op A valid function object.
+     */
+    template<typename Op>
+    inline void data(Op op) const ENTT_NOEXCEPT {
+        internal::iterate<&internal::meta_type_node::data>([op = std::move(op)](auto *node) {
+            op(node->meta());
+        }, node);
+    }
+
+    /**
+     * @brief Returns the meta data associated with a given name.
+     *
+     * Searches recursively among **all** the meta data of the given type. This
+     * means that the meta data of the base classes will also be inspected, if
+     * any.
+     *
+     * @param str The name to use to search for a meta data.
+     * @return The meta data associated with the given name, if any.
+     */
+    inline meta_data * data(const char *str) const ENTT_NOEXCEPT {
+        return internal::find_if<&internal::meta_type_node::data>([name = hashed_string{str}](auto *node) {
+            return node->name == name;
+        }, node);
+    }
+
+    /**
+     * @brief Iterates all the meta functions of a meta type.
+     *
+     * Iteratively returns **all** the meta functions of the given type. This
+     * means that the meta functions of the base classes will also be returned,
+     * if any.
+     *
+     * @tparam Op Type of the function object to invoke.
+     * @param op A valid function object.
+     */
+    template<typename Op>
+    inline void func(Op op) const ENTT_NOEXCEPT {
+        internal::iterate<&internal::meta_type_node::func>([op = std::move(op)](auto *node) {
+            op(node->meta());
+        }, node);
+    }
+
+    /**
+     * @brief Returns the meta function associated with a given name.
+     *
+     * Searches recursively among **all** the meta functions of the given type.
+     * This means that the meta functions of the base classes will also be
+     * inspected, if any.
+     *
+     * @param str The name to use to search for a meta function.
+     * @return The meta function associated with the given name, if any.
+     */
+    inline meta_func * func(const char *str) const ENTT_NOEXCEPT {
+        return internal::find_if<&internal::meta_type_node::func>([name = hashed_string{str}](auto *node) {
+            return node->name == name;
+        }, node);
+    }
+
+    /**
+     * @brief Creates an instance of the underlying type, if possible.
+     *
+     * To create a valid instance, the types of the parameters must coincide
+     * exactly with those required by the underlying meta constructor.
+     * Otherwise, an empty and then invalid container is returned.
+     *
+     * @tparam Args Types of arguments to use to construct the instance.
+     * @param args Parameters to use to construct the instance.
+     * @return A meta any containing the new instance, if any.
+     */
+    template<typename... Args>
+    meta_any construct(Args &&... args) const {
+        std::array<meta_any, sizeof...(Args)> arguments{{std::forward<Args>(args)...}};
+        meta_any any{};
+
+        internal::iterate<&internal::meta_type_node::ctor>([data = arguments.data(), &any](auto *node) -> bool {
+            any = node->invoke(data);
+            return static_cast<bool>(any);
+        }, node);
+
+        return any;
+    }
+
+    /**
+     * @brief Destroys an instance of the underlying type.
+     *
+     * It must be possible to cast the instance to the underlying type.
+     * Otherwise, invoking the meta destructor results in an undefined behavior.
+     *
+     * @param handle An opaque pointer to an instance of the underlying type.
+     * @return True in case of success, false otherwise.
+     */
+    inline bool destroy(meta_handle handle) const {
+        return node->dtor ? node->dtor->invoke(handle) : node->destroy(handle);
+    }
+
+    /**
+     * @brief Iterates all the properties assigned to a meta type.
+     *
+     * Iteratively returns **all** the properties of the given type. This means
+     * that the properties of the base classes will also be returned, if any.
+     *
+     * @tparam Op Type of the function object to invoke.
+     * @param op A valid function object.
+     */
+    template<typename Op>
+    inline std::enable_if_t<std::is_invocable_v<Op, meta_prop *>, void>
+    prop(Op op) const ENTT_NOEXCEPT {
+        internal::iterate<&internal::meta_type_node::prop>([op = std::move(op)](auto *node) {
+            op(node->meta());
+        }, node);
+    }
+
+    /**
+     * @brief Returns the property associated with a given key.
+     *
+     * Searches recursively among **all** the properties of the given type. This
+     * means that the properties of the base classes will also be inspected, if
+     * any.
+     *
+     * @tparam Key Type of key to use to search for a property.
+     * @param key The key to use to search for a property.
+     * @return The property associated with the given key, if any.
+     */
+    template<typename Key>
+    inline std::enable_if_t<!std::is_invocable_v<Key, meta_prop *>, meta_prop *>
+    prop(Key &&key) const ENTT_NOEXCEPT {
+        return internal::find_if<&internal::meta_type_node::prop>([key = meta_any{std::forward<Key>(key)}](auto *curr) {
+            return curr->key() == key;
+        }, node);
+    }
+
+private:
+    internal::meta_type_node *node;
+};
+
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+
+namespace internal {
+
+
+template<typename...>
+struct meta_function_helper;
+
+
+template<typename Ret, typename... Args>
+struct meta_function_helper<Ret(Args...)> {
+    using return_type = Ret;
+    using args_type = std::tuple<Args...>;
+
+    template<std::size_t Index>
+    using arg_type = std::decay_t<std::tuple_element_t<Index, args_type>>;
+
+    static constexpr auto size = sizeof...(Args);
+
+    inline static auto arg(typename meta_func_node::size_type index) {
+        return std::array<meta_type_node *, sizeof...(Args)>{{meta_info<Args>::resolve()...}}[index];
+    }
+};
+
+
+template<typename Class, typename Ret, typename... Args, bool Const, bool Static>
+struct meta_function_helper<Class, Ret(Args...), std::bool_constant<Const>, std::bool_constant<Static>>: meta_function_helper<Ret(Args...)> {
+    using class_type = Class;
+    static constexpr auto is_const = Const;
+    static constexpr auto is_static = Static;
+};
+
+
+template<typename Ret, typename... Args, typename Class>
+constexpr meta_function_helper<Class, Ret(Args...), std::bool_constant<false>, std::bool_constant<false>>
+to_meta_function_helper(Ret(Class:: *)(Args...));
+
+
+template<typename Ret, typename... Args, typename Class>
+constexpr meta_function_helper<Class, Ret(Args...), std::bool_constant<true>, std::bool_constant<false>>
+to_meta_function_helper(Ret(Class:: *)(Args...) const);
+
+
+template<typename Ret, typename... Args>
+constexpr meta_function_helper<void, Ret(Args...), std::bool_constant<false>, std::bool_constant<true>>
+to_meta_function_helper(Ret(*)(Args...));
+
+
+template<auto Func>
+struct meta_function_helper<std::integral_constant<decltype(Func), Func>>: decltype(to_meta_function_helper(Func)) {};
+
+
+template<typename Type>
+inline bool destroy([[maybe_unused]] meta_handle handle) {
+    if constexpr(std::is_void_v<Type>) {
+        return false;
+    } else {
+        return handle.type() == meta_info<Type>::resolve()->meta()
+                ? (static_cast<Type *>(handle.data())->~Type(), true)
+                : false;
+    }
+}
+
+
+template<typename Type, typename... Args, std::size_t... Indexes>
+inline meta_any construct(meta_any * const args, std::index_sequence<Indexes...>) {
+    std::array<bool, sizeof...(Args)> can_cast{{(args+Indexes)->can_cast<std::decay_t<Args>>()...}};
+    std::array<bool, sizeof...(Args)> can_convert{{(std::get<Indexes>(can_cast) ? false : (args+Indexes)->can_convert<std::decay_t<Args>>())...}};
+    meta_any any{};
+
+    if(((std::get<Indexes>(can_cast) || std::get<Indexes>(can_convert)) && ...)) {
+        ((std::get<Indexes>(can_convert) ? void((args+Indexes)->convert<std::decay_t<Args>>()) : void()), ...);
+        any = Type{(args+Indexes)->cast<std::decay_t<Args>>()...};
+    }
+
+    return any;
+}
+
+
+template<bool Const, typename Type, auto Data>
+bool setter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any &any) {
+    if constexpr(Const) {
+        return false;
+    } else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
+        using data_type = std::decay_t<decltype(std::declval<Type>().*Data)>;
+        static_assert(std::is_invocable_v<decltype(Data), Type>);
+        const bool accepted = any.can_cast<data_type>() || any.convert<data_type>();
+        auto *clazz = handle.try_cast<Type>();
+
+        if(accepted && clazz) {
+            clazz->*Data = any.cast<data_type>();
+        }
+
+        return accepted;
+    } else {
+        using data_type = std::decay_t<decltype(*Data)>;
+        const bool accepted = any.can_cast<data_type>() || any.convert<data_type>();
+
+        if(accepted) {
+            *Data = any.cast<data_type>();
+        }
+
+        return accepted;
+    }
+}
+
+
+template<typename Type, auto Data>
+inline meta_any getter([[maybe_unused]] meta_handle handle) {
+    if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
+        static_assert(std::is_invocable_v<decltype(Data), Type>);
+        auto *clazz = handle.try_cast<Type>();
+        return clazz ? meta_any{clazz->*Data} : meta_any{};
+    } else {
+        return meta_any{*Data};
+    }
+}
+
+
+template<typename Type, auto Func, std::size_t... Indexes>
+std::enable_if_t<std::is_function_v<std::remove_pointer_t<decltype(Func)>>, meta_any>
+invoke(const meta_handle &, meta_any *args, std::index_sequence<Indexes...>) {
+    using helper_type = meta_function_helper<std::integral_constant<decltype(Func), Func>>;
+    meta_any any{};
+
+    if((((args+Indexes)->can_cast<typename helper_type::template arg_type<Indexes>>()
+            || (args+Indexes)->convert<typename helper_type::template arg_type<Indexes>>()) && ...))
+    {
+        if constexpr(std::is_void_v<typename helper_type::return_type>) {
+            (*Func)((args+Indexes)->cast<typename helper_type::template arg_type<Indexes>>()...);
+        } else {
+            any = meta_any{(*Func)((args+Indexes)->cast<typename helper_type::template arg_type<Indexes>>()...)};
+        }
+    }
+
+    return any;
+}
+
+
+template<typename Type, auto Member, std::size_t... Indexes>
+std::enable_if_t<std::is_member_function_pointer_v<decltype(Member)>, meta_any>
+invoke(meta_handle &handle, meta_any *args, std::index_sequence<Indexes...>) {
+    using helper_type = meta_function_helper<std::integral_constant<decltype(Member), Member>>;
+    static_assert(std::is_base_of_v<typename helper_type::class_type, Type>);
+    auto *clazz = handle.try_cast<Type>();
+    meta_any any{};
+
+    if(clazz && (((args+Indexes)->can_cast<typename helper_type::template arg_type<Indexes>>()
+                  || (args+Indexes)->convert<typename helper_type::template arg_type<Indexes>>()) && ...))
+    {
+        if constexpr(std::is_void_v<typename helper_type::return_type>) {
+            (clazz->*Member)((args+Indexes)->cast<typename helper_type::template arg_type<Indexes>>()...);
+        } else {
+            any = meta_any{(clazz->*Member)((args+Indexes)->cast<typename helper_type::template arg_type<Indexes>>()...)};
+        }
+    }
+
+    return any;
+}
+
+
+template<typename Type>
+meta_type_node * meta_node<Type>::resolve() ENTT_NOEXCEPT {
+    static_assert((std::is_scalar_v<Type> || std::is_class_v<Type> || std::is_void_v<Type>) && !std::is_const_v<Type>);
+
+    if(!type) {
+        static meta_type_node node{
+            {},
+            nullptr,
+            nullptr,
+            std::is_void_v<Type>,
+            std::is_enum_v<Type>,
+            std::is_class_v<Type>,
+            std::is_pointer_v<Type>,
+            std::is_pointer_v<Type> && std::is_function_v<std::remove_pointer_t<Type>>,
+            std::is_member_object_pointer_v<Type>,
+            std::is_member_function_pointer_v<Type>,
+            std::is_member_pointer_v<Type>,
+            std::is_arithmetic_v<Type>,
+            std::is_compound_v<Type>,
+            &destroy<Type>,
+            []() {
+                static meta_type meta{&node};
+                return &meta;
+            }
+        };
+
+        type = &node;
+    }
+
+    return type;
+}
+
+
+}
+
+
+/**
+ * Internal details not to be documented.
+ * @endcond TURN_OFF_DOXYGEN
+ */
+
+
+}
+
+
+#endif // ENTT_META_META_HPP

+ 2 - 1
src/entt/resource/handle.hpp

@@ -101,7 +101,8 @@ public:
     }
 
     /**
-     * @brief Returns true if the handle contains a resource, false otherwise.
+     * @brief Returns true if a handle contains a resource, false otherwise.
+     * @return True if the handle contains a resource, false otherwise.
      */
     explicit operator bool() const { return static_cast<bool>(resource); }
 

+ 1 - 1
src/entt/signal/dispatcher.hpp

@@ -98,7 +98,7 @@ public:
      *
      * The function type for a listener is:
      * @code{.cpp}
-     * void(const Event &)
+     * void(const Event &);
      * @endcode
      *
      * The order of invocation of the listeners isn't guaranteed.

+ 5 - 0
test/CMakeLists.txt

@@ -63,6 +63,7 @@ SETUP_AND_ADD_TEST(family entt/core/family.cpp)
 SETUP_AND_ADD_TEST(hashed_string entt/core/hashed_string.cpp)
 SETUP_AND_ADD_TEST(ident entt/core/ident.cpp)
 SETUP_AND_ADD_TEST(monostate entt/core/monostate.cpp)
+SETUP_AND_ADD_TEST(utility entt/core/utility.cpp)
 
 # Test entity
 
@@ -79,6 +80,10 @@ SETUP_AND_ADD_TEST(view entt/entity/view.cpp)
 
 SETUP_AND_ADD_TEST(locator entt/locator/locator.cpp)
 
+# Test meta
+
+SETUP_AND_ADD_TEST(meta entt/meta/meta.cpp)
+
 # Test process
 
 SETUP_AND_ADD_TEST(process entt/process/process.cpp)

+ 0 - 14
test/entt/core/algorithm.cpp

@@ -24,17 +24,3 @@ TEST(Algorithm, InsertionSort) {
         ASSERT_LT(arr[i], arr[i+1]);
     }
 }
-
-TEST(Algorithm, OneShotBubbleSort) {
-    std::array<int, 5> arr{{4, 1, 3, 2, 0}};
-    entt::one_shot_bubble_sort sort;
-
-    sort(arr.begin(), arr.end());
-    sort(arr.begin(), arr.end());
-    sort(arr.begin(), arr.end());
-    sort(arr.begin(), arr.end());
-
-    for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
-        ASSERT_LT(arr[i], arr[i+1]);
-    }
-}

+ 12 - 12
test/entt/core/family.cpp

@@ -1,22 +1,22 @@
 #include <gtest/gtest.h>
 #include <entt/core/family.hpp>
 
-using my_family = entt::family<struct my_family_type>;
-using your_family = entt::family<struct your_family_type>;
+using a_family = entt::family<struct a_family_type>;
+using another_family = entt::family<struct another_family_type>;
 
 TEST(Family, Functionalities) {
-    auto a_family_type = my_family::type<struct family_type_a>;
-    auto same_family_type = my_family::type<struct family_type_a>;
-    auto another_family_type = my_family::type<struct family_type_b>;
-    auto your_family_type = your_family::type<struct family_type_c>;
+    auto t1 = a_family::type<int>;
+    auto t2 = a_family::type<int>;
+    auto t3 = a_family::type<char>;
+    auto t4 = another_family::type<double>;
 
-    ASSERT_EQ(a_family_type, same_family_type);
-    ASSERT_NE(a_family_type, another_family_type);
-    ASSERT_EQ(a_family_type, your_family_type);
+    ASSERT_EQ(t1, t2);
+    ASSERT_NE(t1, t3);
+    ASSERT_EQ(t1, t4);
 }
 
 TEST(Family, Uniqueness) {
-    ASSERT_EQ(my_family::type<int>, my_family::type<int &>);
-    ASSERT_EQ(my_family::type<int>, my_family::type<int &&>);
-    ASSERT_EQ(my_family::type<int>, my_family::type<const int &>);
+    ASSERT_EQ(a_family::type<int>, a_family::type<int &>);
+    ASSERT_EQ(a_family::type<int>, a_family::type<int &&>);
+    ASSERT_EQ(a_family::type<int>, a_family::type<const int &>);
 }

+ 9 - 0
test/entt/core/hashed_string.cpp

@@ -28,6 +28,15 @@ TEST(HashedString, Functionalities) {
     ASSERT_NE(bar_hs, "foo"_hs);
 }
 
+TEST(HashedString, Empty) {
+    using hash_type = entt::hashed_string::hash_type;
+
+    entt::hashed_string hs{};
+
+    ASSERT_EQ(static_cast<hash_type>(hs), hash_type{});
+    ASSERT_EQ(static_cast<const char *>(hs), nullptr);
+}
+
 TEST(HashedString, Constexprness) {
     using hash_type = entt::hashed_string::hash_type;
     // how would you test a constexpr otherwise?

+ 19 - 0
test/entt/core/utility.cpp

@@ -0,0 +1,19 @@
+#include <gtest/gtest.h>
+#include <entt/core/utility.hpp>
+
+struct Functions {
+    static void foo(int) {}
+    static void foo() {}
+
+    void bar(int) {}
+    void bar() {}
+};
+
+
+TEST(Utility, Overload) {
+    ASSERT_EQ(entt::overload<void(int)>(&Functions::foo), static_cast<void(*)(int)>(&Functions::foo));
+    ASSERT_EQ(entt::overload<void()>(&Functions::foo), static_cast<void(*)()>(&Functions::foo));
+
+    ASSERT_EQ(entt::overload<void(int)>(&Functions::bar), static_cast<void(Functions:: *)(int)>(&Functions::bar));
+    ASSERT_EQ(entt::overload<void()>(&Functions::bar), static_cast<void(Functions:: *)()>(&Functions::bar));
+}

+ 1332 - 0
test/entt/meta/meta.cpp

@@ -0,0 +1,1332 @@
+#include <type_traits>
+#include <gtest/gtest.h>
+#include <entt/core/hashed_string.hpp>
+#include <entt/core/utility.hpp>
+#include <entt/meta/factory.hpp>
+#include <entt/meta/meta.hpp>
+
+enum class properties {
+    prop_int,
+    prop_bool
+};
+
+struct empty_type {
+    virtual ~empty_type() = default;
+
+    static void destroy(empty_type &instance) {
+        ++counter;
+    }
+
+    inline static int counter = 0;
+};
+
+struct fat_type: empty_type {
+    fat_type() = default;
+
+    fat_type(int *value)
+        : foo{value}, bar{value}
+    {}
+
+    int *foo{nullptr};
+    int *bar{nullptr};
+
+    bool operator==(const fat_type &other) const {
+        return foo == other.foo && bar == other.bar;
+    }
+};
+
+bool operator!=(const fat_type &lhs, const fat_type &rhs) {
+    return !(lhs == rhs);
+}
+
+struct base_type {
+    virtual ~base_type() = default;
+};
+
+struct derived_type: base_type {
+    derived_type() = default;
+    derived_type(const base_type &, int i, char c): i{i}, c{c} {}
+
+    const int i{};
+    const char c{};
+};
+
+derived_type derived_factory(const base_type &, int value) {
+    return {derived_type{}, value, 'c'};
+}
+
+struct data_type {
+    int i{0};
+    const int j{1};
+    inline static int h{2};
+    inline static const int k{3};
+    empty_type empty{};
+};
+
+struct func_type {
+    int f(const base_type &, 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; }
+    void g(int v) { value = v*v; }
+
+    static int h(int v) { return v; }
+    static void k(int v) { value = v; }
+
+    inline static int value = 0;
+};
+
+struct not_comparable_type {
+    bool operator==(const not_comparable_type &) const = delete;
+};
+
+bool operator!=(const not_comparable_type &, const not_comparable_type &) = delete;
+
+struct an_abstract_type {
+    virtual ~an_abstract_type() = default;
+    void f(int v) { i = v; }
+    virtual void g(int) = 0;
+    int i{};
+};
+
+struct another_abstract_type {
+    virtual ~another_abstract_type() = default;
+    virtual void h(char) = 0;
+    char j{};
+};
+
+struct concrete_type: an_abstract_type, another_abstract_type {
+    void f(int v) { i = v*v; } // hide, it's ok :-)
+    void g(int v) override { i = -v; }
+    void h(char c) override { j = c; }
+};
+
+struct Meta: public ::testing::Test {
+    static void SetUpTestCase() {
+        entt::reflect<double>().conv<int>();
+
+        entt::reflect<char>("char", std::make_pair(properties::prop_int, 42));
+
+        entt::reflect<base_type>("base");
+
+        entt::reflect<derived_type>("derived", std::make_pair(properties::prop_int, 99))
+                .base<base_type>()
+                .ctor<const base_type &, int, char>(std::make_pair(properties::prop_bool, false))
+                .ctor<&derived_factory>(std::make_pair(properties::prop_int, 42));
+
+        entt::reflect<empty_type>("empty")
+                .dtor<&empty_type::destroy>();
+
+        entt::reflect<fat_type>("fat")
+                .base<empty_type>()
+                .dtor<&fat_type::destroy>();
+
+        entt::reflect<data_type>("data")
+                .data<&data_type::i>("i", std::make_pair(properties::prop_int, 0))
+                .data<&data_type::j>("j", std::make_pair(properties::prop_int, 1))
+                .data<&data_type::h>("h", std::make_pair(properties::prop_int, 2))
+                .data<&data_type::k>("k", std::make_pair(properties::prop_int, 3))
+                .data<&data_type::empty>("empty");
+
+        entt::reflect<func_type>("func")
+                .func<entt::overload<int(const base_type &, int, int)>(&func_type::f)>("f3")
+                .func<entt::overload<int(int, int)>(&func_type::f)>("f2", std::make_pair(properties::prop_bool, false))
+                .func<entt::overload<int(int) const>(&func_type::f)>("f1", std::make_pair(properties::prop_bool, false))
+                .func<&func_type::g>("g", std::make_pair(properties::prop_bool, false))
+                .func<&func_type::h>("h", std::make_pair(properties::prop_bool, false))
+                .func<&func_type::k>("k", std::make_pair(properties::prop_bool, false));
+
+        entt::reflect<an_abstract_type>("an_abstract_type", std::make_pair(properties::prop_bool, false))
+                .data<&an_abstract_type::i>("i")
+                .func<&an_abstract_type::f>("f")
+                .func<&an_abstract_type::g>("g");
+
+        entt::reflect<another_abstract_type>("another_abstract_type", std::make_pair(properties::prop_int, 42))
+                .data<&another_abstract_type::j>("j")
+                .func<&another_abstract_type::h>("h");
+
+        entt::reflect<concrete_type>("concrete")
+                .base<an_abstract_type>()
+                .base<another_abstract_type>()
+                .func<&concrete_type::f>("f");
+    }
+
+    void SetUp() override {
+        empty_type::counter = 0;
+        func_type::value = 0;
+    }
+};
+
+TEST_F(Meta, Resolve) {
+    ASSERT_EQ(entt::resolve<derived_type>(), entt::resolve("derived"));
+
+    bool found = false;
+
+    entt::resolve([&found](auto *type) {
+        found = found || type == entt::resolve<derived_type>();
+    });
+
+    ASSERT_TRUE(found);
+}
+
+TEST_F(Meta, MetaAnySBO) {
+    entt::meta_any any{'c'};
+
+    ASSERT_TRUE(any);
+    ASSERT_FALSE(any.can_cast<void>());
+    ASSERT_TRUE(any.can_cast<char>());
+    ASSERT_EQ(any.cast<char>(), 'c');
+    ASSERT_EQ(std::as_const(any).cast<char>(), 'c');
+    ASSERT_NE(any.data(), nullptr);
+    ASSERT_NE(std::as_const(any).data(), nullptr);
+    ASSERT_EQ(any, entt::meta_any{'c'});
+    ASSERT_NE(any, entt::meta_any{'h'});
+}
+
+TEST_F(Meta, MetaAnyNoSBO) {
+    int value = 42;
+    fat_type instance{&value};
+    entt::meta_any any{instance};
+
+    ASSERT_TRUE(any);
+    ASSERT_FALSE(any.can_cast<void>());
+    ASSERT_TRUE(any.can_cast<fat_type>());
+    ASSERT_EQ(any.cast<fat_type>(), instance);
+    ASSERT_EQ(std::as_const(any).cast<fat_type>(), instance);
+    ASSERT_NE(any.data(), nullptr);
+    ASSERT_NE(std::as_const(any).data(), nullptr);
+    ASSERT_EQ(any, entt::meta_any{instance});
+    ASSERT_NE(any, fat_type{});
+}
+
+TEST_F(Meta, MetaAnyEmpty) {
+    entt::meta_any any{};
+
+    ASSERT_FALSE(any);
+    ASSERT_EQ(any.type(), nullptr);
+    ASSERT_FALSE(any.can_cast<void>());
+    ASSERT_FALSE(any.can_cast<empty_type>());
+    ASSERT_EQ(any.data(), nullptr);
+    ASSERT_EQ(std::as_const(any).data(), nullptr);
+    ASSERT_EQ(any, entt::meta_any{});
+    ASSERT_NE(any, entt::meta_any{'c'});
+}
+
+TEST_F(Meta, MetaAnySBOCopyConstruction) {
+    entt::meta_any any{42};
+    entt::meta_any other{any};
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(other.can_cast<void>());
+    ASSERT_TRUE(other.can_cast<int>());
+    ASSERT_EQ(other.cast<int>(), 42);
+    ASSERT_EQ(std::as_const(other).cast<int>(), 42);
+    ASSERT_EQ(other, entt::meta_any{42});
+    ASSERT_NE(other, entt::meta_any{0});
+}
+
+TEST_F(Meta, MetaAnySBOCopyAssignment) {
+    entt::meta_any any{42};
+    entt::meta_any other{};
+
+    other = any;
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(other.can_cast<void>());
+    ASSERT_TRUE(other.can_cast<int>());
+    ASSERT_EQ(other.cast<int>(), 42);
+    ASSERT_EQ(std::as_const(other).cast<int>(), 42);
+    ASSERT_EQ(other, entt::meta_any{42});
+    ASSERT_NE(other, entt::meta_any{0});
+}
+
+TEST_F(Meta, MetaAnySBOMoveConstruction) {
+    entt::meta_any any{42};
+    entt::meta_any other{std::move(any)};
+
+    ASSERT_FALSE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(other.can_cast<void>());
+    ASSERT_TRUE(other.can_cast<int>());
+    ASSERT_EQ(other.cast<int>(), 42);
+    ASSERT_EQ(std::as_const(other).cast<int>(), 42);
+    ASSERT_EQ(other, entt::meta_any{42});
+    ASSERT_NE(other, entt::meta_any{0});
+}
+
+TEST_F(Meta, MetaAnySBOMoveAssignment) {
+    entt::meta_any any{42};
+    entt::meta_any other{};
+
+    other = std::move(any);
+
+    ASSERT_FALSE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(other.can_cast<void>());
+    ASSERT_TRUE(other.can_cast<int>());
+    ASSERT_EQ(other.cast<int>(), 42);
+    ASSERT_EQ(std::as_const(other).cast<int>(), 42);
+    ASSERT_EQ(other, entt::meta_any{42});
+    ASSERT_NE(other, entt::meta_any{0});
+}
+
+TEST_F(Meta, MetaAnyNoSBOCopyConstruction) {
+    int value = 42;
+    fat_type instance{&value};
+    entt::meta_any any{instance};
+    entt::meta_any other{any};
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(other.can_cast<void>());
+    ASSERT_TRUE(other.can_cast<fat_type>());
+    ASSERT_EQ(other.cast<fat_type>(), instance);
+    ASSERT_EQ(std::as_const(other).cast<fat_type>(), instance);
+    ASSERT_EQ(other, entt::meta_any{instance});
+    ASSERT_NE(other, fat_type{});
+}
+
+TEST_F(Meta, MetaAnyNoSBOCopyAssignment) {
+    int value = 42;
+    fat_type instance{&value};
+    entt::meta_any any{instance};
+    entt::meta_any other{};
+
+    other = any;
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(other.can_cast<void>());
+    ASSERT_TRUE(other.can_cast<fat_type>());
+    ASSERT_EQ(other.cast<fat_type>(), instance);
+    ASSERT_EQ(std::as_const(other).cast<fat_type>(), instance);
+    ASSERT_EQ(other, entt::meta_any{instance});
+    ASSERT_NE(other, fat_type{});
+}
+
+TEST_F(Meta, MetaAnyNoSBOMoveConstruction) {
+    int value = 42;
+    fat_type instance{&value};
+    entt::meta_any any{instance};
+    entt::meta_any other{std::move(any)};
+
+    ASSERT_FALSE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(other.can_cast<void>());
+    ASSERT_TRUE(other.can_cast<fat_type>());
+    ASSERT_EQ(other.cast<fat_type>(), instance);
+    ASSERT_EQ(std::as_const(other).cast<fat_type>(), instance);
+    ASSERT_EQ(other, entt::meta_any{instance});
+    ASSERT_NE(other, fat_type{});
+}
+
+TEST_F(Meta, MetaAnyNoSBOMoveAssignment) {
+    int value = 42;
+    fat_type instance{&value};
+    entt::meta_any any{instance};
+    entt::meta_any other{};
+
+    other = std::move(any);
+
+    ASSERT_FALSE(any);
+    ASSERT_TRUE(other);
+    ASSERT_FALSE(other.can_cast<void>());
+    ASSERT_TRUE(other.can_cast<fat_type>());
+    ASSERT_EQ(other.cast<fat_type>(), instance);
+    ASSERT_EQ(std::as_const(other).cast<fat_type>(), instance);
+    ASSERT_EQ(other, entt::meta_any{instance});
+    ASSERT_NE(other, fat_type{});
+}
+
+TEST_F(Meta, MetaAnySBODestruction) {
+    ASSERT_EQ(empty_type::counter, 0);
+    entt::meta_any any{empty_type{}};
+    any = {};
+    ASSERT_EQ(empty_type::counter, 1);
+}
+
+TEST_F(Meta, MetaAnyNoSBODestruction) {
+    ASSERT_EQ(fat_type::counter, 0);
+    entt::meta_any any{fat_type{}};
+    any = {};
+    ASSERT_EQ(fat_type::counter, 1);
+}
+
+TEST_F(Meta, MetaAnySBOSwap) {
+    entt::meta_any lhs{'c'};
+    entt::meta_any rhs{42};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_TRUE(lhs.can_cast<int>());
+    ASSERT_EQ(lhs.cast<int>(), 42);
+    ASSERT_TRUE(rhs.can_cast<char>());
+    ASSERT_EQ(rhs.cast<char>(), 'c');
+}
+
+TEST_F(Meta, MetaAnyNoSBOSwap) {
+    int i, j;
+    entt::meta_any lhs{fat_type{&i}};
+    entt::meta_any rhs{fat_type{&j}};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_EQ(lhs.cast<fat_type>().foo, &j);
+    ASSERT_EQ(rhs.cast<fat_type>().bar, &i);
+}
+
+TEST_F(Meta, MetaAnySBOWithNoSBOSwap) {
+    int value = 42;
+    entt::meta_any lhs{fat_type{&value}};
+    entt::meta_any rhs{'c'};
+
+    std::swap(lhs, rhs);
+
+    ASSERT_TRUE(lhs.can_cast<char>());
+    ASSERT_EQ(lhs.cast<char>(), 'c');
+    ASSERT_TRUE(rhs.can_cast<fat_type>());
+    ASSERT_EQ(rhs.cast<fat_type>().foo, &value);
+    ASSERT_EQ(rhs.cast<fat_type>().bar, &value);
+}
+
+TEST_F(Meta, MetaAnyComparable) {
+    entt::meta_any any{'c'};
+
+    ASSERT_EQ(any, any);
+    ASSERT_EQ(any, entt::meta_any{'c'});
+    ASSERT_NE(any, entt::meta_any{'a'});
+    ASSERT_NE(any, entt::meta_any{});
+
+    ASSERT_TRUE(any == any);
+    ASSERT_TRUE(any == entt::meta_any{'c'});
+    ASSERT_FALSE(any == entt::meta_any{'a'});
+    ASSERT_TRUE(any != entt::meta_any{'a'});
+    ASSERT_TRUE(any != entt::meta_any{});
+}
+
+TEST_F(Meta, MetaAnyNotComparable) {
+    entt::meta_any any{not_comparable_type{}};
+
+    ASSERT_EQ(any, any);
+    ASSERT_NE(any, entt::meta_any{not_comparable_type{}});
+    ASSERT_NE(any, entt::meta_any{});
+
+    ASSERT_TRUE(any == any);
+    ASSERT_FALSE(any == entt::meta_any{not_comparable_type{}});
+    ASSERT_TRUE(any != entt::meta_any{});
+}
+
+TEST_F(Meta, MetaAnyCast) {
+    entt::meta_any any{derived_type{}};
+    entt::meta_handle handle{any};
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::resolve<derived_type>());
+    ASSERT_FALSE(any.can_cast<void>());
+    ASSERT_TRUE(any.can_cast<base_type>());
+    ASSERT_TRUE(any.can_cast<derived_type>());
+    ASSERT_EQ(&any.cast<base_type>(), handle.try_cast<base_type>());
+    ASSERT_EQ(&any.cast<derived_type>(), handle.try_cast<derived_type>());
+    ASSERT_EQ(&std::as_const(any).cast<base_type>(), handle.try_cast<base_type>());
+    ASSERT_EQ(&std::as_const(any).cast<derived_type>(), handle.try_cast<derived_type>());
+}
+
+TEST_F(Meta, MetaAnyConvert) {
+    entt::meta_any any{42.};
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::resolve<double>());
+    ASSERT_FALSE(any.can_convert<char>());
+    ASSERT_TRUE(any.can_convert<double>());
+    ASSERT_TRUE(any.can_convert<int>());
+
+    ASSERT_TRUE(any.convert<double>());
+    ASSERT_FALSE(any.convert<char>());
+
+    ASSERT_EQ(any.type(), entt::resolve<double>());
+    ASSERT_EQ(any.cast<double>(), 42.);
+
+    ASSERT_TRUE(any.convert<int>());
+
+    ASSERT_EQ(any.type(), entt::resolve<int>());
+    ASSERT_EQ(any.cast<int>(), 42);
+}
+
+TEST_F(Meta, MetaAnyConstConvert) {
+    const entt::meta_any any{42.};
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::resolve<double>());
+    ASSERT_FALSE(any.can_convert<char>());
+    ASSERT_TRUE(any.can_convert<double>());
+    ASSERT_TRUE(any.can_convert<int>());
+
+    ASSERT_TRUE(any.convert<double>());
+    ASSERT_FALSE(any.convert<char>());
+
+    ASSERT_EQ(any.type(), entt::resolve<double>());
+    ASSERT_EQ(any.cast<double>(), 42.);
+
+    auto other = any.convert<int>();
+
+    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);
+}
+
+TEST_F(Meta, MetaHandleFromObject) {
+    empty_type empty{};
+    entt::meta_handle handle{empty};
+
+    ASSERT_TRUE(handle);
+    ASSERT_EQ(handle.type(), entt::resolve<empty_type>());
+    ASSERT_EQ(handle.try_cast<void>(), nullptr);
+    ASSERT_EQ(handle.try_cast<empty_type>(), &empty);
+    ASSERT_EQ(std::as_const(handle).try_cast<empty_type>(), &empty);
+    ASSERT_EQ(handle.data(), &empty);
+    ASSERT_EQ(std::as_const(handle).data(), &empty);
+}
+
+TEST_F(Meta, MetaHandleFromMetaAny) {
+    entt::meta_any any{42};
+    entt::meta_handle handle{any};
+
+    ASSERT_TRUE(handle);
+    ASSERT_EQ(handle.type(), entt::resolve<int>());
+    ASSERT_EQ(handle.try_cast<void>(), nullptr);
+    ASSERT_EQ(handle.try_cast<int>(), any.data());
+    ASSERT_EQ(std::as_const(handle).try_cast<int>(), any.data());
+    ASSERT_EQ(handle.data(), any.data());
+    ASSERT_EQ(std::as_const(handle).data(), any.data());
+}
+
+TEST_F(Meta, MetaHandleEmpty) {
+    entt::meta_handle handle{};
+
+    ASSERT_FALSE(handle);
+    ASSERT_EQ(handle.type(), nullptr);
+    ASSERT_EQ(handle.try_cast<void>(), nullptr);
+    ASSERT_EQ(handle.try_cast<empty_type>(), nullptr);
+    ASSERT_EQ(handle.data(), nullptr);
+    ASSERT_EQ(std::as_const(handle).data(), nullptr);
+}
+
+TEST_F(Meta, MetaHandleTryCast) {
+    derived_type derived{};
+    base_type *base = &derived;
+    entt::meta_handle handle{derived};
+
+    ASSERT_TRUE(handle);
+    ASSERT_EQ(handle.type(), entt::resolve<derived_type>());
+    ASSERT_EQ(handle.try_cast<void>(), nullptr);
+    ASSERT_EQ(handle.try_cast<base_type>(), base);
+    ASSERT_EQ(handle.try_cast<derived_type>(), &derived);
+    ASSERT_EQ(std::as_const(handle).try_cast<base_type>(), base);
+    ASSERT_EQ(std::as_const(handle).try_cast<derived_type>(), &derived);
+    ASSERT_EQ(handle.data(), &derived);
+    ASSERT_EQ(std::as_const(handle).data(), &derived);
+}
+
+TEST_F(Meta, MetaProp) {
+    auto *prop = entt::resolve<char>()->prop(properties::prop_int);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_int);
+    ASSERT_EQ(prop->value(), 42);
+}
+
+TEST_F(Meta, MetaBase) {
+    auto *base = entt::resolve<derived_type>()->base("base");
+    derived_type derived{};
+
+    ASSERT_NE(base, nullptr);
+    ASSERT_EQ(base->parent(), entt::resolve("derived"));
+    ASSERT_EQ(base->type(), entt::resolve<base_type>());
+    ASSERT_EQ(base->cast(&derived), static_cast<base_type *>(&derived));
+}
+
+TEST_F(Meta, MetaConv) {
+    auto *conv = entt::resolve<double>()->conv<int>();
+    double value = 3.;
+
+    ASSERT_NE(conv, nullptr);
+    ASSERT_EQ(conv->parent(), entt::resolve<double>());
+    ASSERT_EQ(conv->type(), entt::resolve<int>());
+
+    auto any = conv->convert(&value);
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::resolve<int>());
+    ASSERT_EQ(any.cast<int>(), 3);
+}
+
+TEST_F(Meta, MetaCtor) {
+    auto *ctor = entt::resolve<derived_type>()->ctor<const base_type &, int, char>();
+
+    ASSERT_NE(ctor, nullptr);
+    ASSERT_EQ(ctor->parent(), entt::resolve("derived"));
+    ASSERT_EQ(ctor->size(), entt::meta_ctor::size_type{3});
+    ASSERT_EQ(ctor->arg(entt::meta_ctor::size_type{0}), entt::resolve<base_type>());
+    ASSERT_EQ(ctor->arg(entt::meta_ctor::size_type{1}), entt::resolve<int>());
+    ASSERT_EQ(ctor->arg(entt::meta_ctor::size_type{2}), entt::resolve<char>());
+    ASSERT_EQ(ctor->arg(entt::meta_ctor::size_type{3}), nullptr);
+
+    auto any = ctor->invoke(base_type{}, 42, 'c');
+    auto empty = ctor->invoke();
+
+    ASSERT_FALSE(empty);
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(any.can_cast<derived_type>());
+    ASSERT_EQ(any.cast<derived_type>().i, 42);
+    ASSERT_EQ(any.cast<derived_type>().c, 'c');
+
+    ctor->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_bool);
+        ASSERT_EQ(prop->value(), false);
+    });
+
+    ASSERT_EQ(ctor->prop(properties::prop_int), nullptr);
+
+    auto *prop = ctor->prop(properties::prop_bool);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_bool);
+    ASSERT_EQ(prop->value(), false);
+}
+
+TEST_F(Meta, MetaCtorFunc) {
+    auto *ctor = entt::resolve<derived_type>()->ctor<const base_type &, int>();
+
+    ASSERT_NE(ctor, nullptr);
+    ASSERT_EQ(ctor->parent(), entt::resolve("derived"));
+    ASSERT_EQ(ctor->size(), entt::meta_ctor::size_type{2});
+    ASSERT_EQ(ctor->arg(entt::meta_ctor::size_type{0}), entt::resolve<base_type>());
+    ASSERT_EQ(ctor->arg(entt::meta_ctor::size_type{1}), entt::resolve<int>());
+    ASSERT_EQ(ctor->arg(entt::meta_ctor::size_type{2}), nullptr);
+
+    auto any = ctor->invoke(derived_type{}, 42);
+    auto empty = ctor->invoke(3, 'c');
+
+    ASSERT_FALSE(empty);
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(any.can_cast<derived_type>());
+    ASSERT_EQ(any.cast<derived_type>().i, 42);
+    ASSERT_EQ(any.cast<derived_type>().c, 'c');
+
+    ctor->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_int);
+        ASSERT_EQ(prop->value(), 42);
+    });
+
+    ASSERT_EQ(ctor->prop(properties::prop_bool), nullptr);
+
+    auto *prop = ctor->prop(properties::prop_int);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_int);
+    ASSERT_EQ(prop->value(), 42);
+}
+
+TEST_F(Meta, MetaCtorMetaAnyArgs) {
+    auto *ctor = entt::resolve<derived_type>()->ctor<const base_type &, int, char>();
+    auto any = ctor->invoke(base_type{}, entt::meta_any{42}, entt::meta_any{'c'});
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(any.can_cast<derived_type>());
+    ASSERT_EQ(any.cast<derived_type>().i, 42);
+    ASSERT_EQ(any.cast<derived_type>().c, 'c');
+}
+
+TEST_F(Meta, MetaCtorInvalidArgs) {
+    auto *ctor = entt::resolve<derived_type>()->ctor<const base_type &, int, char>();
+    ASSERT_FALSE(ctor->invoke(base_type{}, entt::meta_any{'c'}, entt::meta_any{42}));
+}
+
+TEST_F(Meta, MetaCtorCastAndConvert) {
+    auto *ctor = entt::resolve<derived_type>()->ctor<const base_type &, int, char>();
+    auto any = ctor->invoke(entt::meta_any{derived_type{}}, entt::meta_any{42.}, entt::meta_any{'c'});
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(any.can_cast<derived_type>());
+    ASSERT_EQ(any.cast<derived_type>().i, 42);
+    ASSERT_EQ(any.cast<derived_type>().c, 'c');
+}
+
+TEST_F(Meta, MetaCtorFuncMetaAnyArgs) {
+    auto *ctor = entt::resolve<derived_type>()->ctor<const base_type &, int>();
+    auto any = ctor->invoke(base_type{}, entt::meta_any{42});
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(any.can_cast<derived_type>());
+    ASSERT_EQ(any.cast<derived_type>().i, 42);
+    ASSERT_EQ(any.cast<derived_type>().c, 'c');
+}
+
+TEST_F(Meta, MetaCtorFuncInvalidArgs) {
+    auto *ctor = entt::resolve<derived_type>()->ctor<const base_type &, int>();
+    ASSERT_FALSE(ctor->invoke(base_type{}, entt::meta_any{'c'}));
+}
+
+TEST_F(Meta, MetaCtorFuncCastAndConvert) {
+    auto *ctor = entt::resolve<derived_type>()->ctor<const base_type &, int>();
+    auto any = ctor->invoke(entt::meta_any{derived_type{}}, entt::meta_any{42.});
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(any.can_cast<derived_type>());
+    ASSERT_EQ(any.cast<derived_type>().i, 42);
+    ASSERT_EQ(any.cast<derived_type>().c, 'c');
+}
+
+TEST_F(Meta, MetaDtor) {
+    auto *dtor = entt::resolve<empty_type>()->dtor();
+    empty_type empty{};
+
+    ASSERT_NE(dtor, nullptr);
+    ASSERT_EQ(dtor->parent(), entt::resolve("empty"));
+    ASSERT_EQ(empty_type::counter, 0);
+    ASSERT_TRUE(dtor->invoke(empty));
+    ASSERT_EQ(empty_type::counter, 1);
+}
+
+TEST_F(Meta, MetaDtorMetaAnyArg) {
+    auto *dtor = entt::resolve<empty_type>()->dtor();
+    entt::meta_any any{empty_type{}};
+
+    ASSERT_EQ(empty_type::counter, 0);
+    ASSERT_TRUE(dtor->invoke(any));
+    ASSERT_EQ(empty_type::counter, 1);
+}
+
+TEST_F(Meta, MetaDtorMetaAnyInvalidArg) {
+    auto *dtor = entt::resolve<empty_type>()->dtor();
+    ASSERT_FALSE(dtor->invoke(int{}));
+}
+
+
+TEST_F(Meta, MetaData) {
+    auto *data = entt::resolve<data_type>()->data("i");
+    data_type instance{};
+
+    ASSERT_NE(data, nullptr);
+    ASSERT_EQ(data->parent(), entt::resolve("data"));
+    ASSERT_EQ(data->type(), entt::resolve<int>());
+    ASSERT_STREQ(data->name(), "i");
+    ASSERT_FALSE(data->is_const());
+    ASSERT_FALSE(data->is_static());
+    ASSERT_EQ(data->get(instance).cast<int>(), 0);
+    ASSERT_TRUE(data->set(instance, 42));
+    ASSERT_EQ(data->get(instance).cast<int>(), 42);
+
+    data->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_int);
+        ASSERT_EQ(prop->value(), 0);
+    });
+
+    ASSERT_EQ(data->prop(properties::prop_bool), nullptr);
+
+    auto *prop = data->prop(properties::prop_int);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_int);
+    ASSERT_EQ(prop->value(), 0);
+}
+
+TEST_F(Meta, MetaDataConst) {
+    auto *data = entt::resolve<data_type>()->data("j");
+    data_type instance{};
+
+    ASSERT_NE(data, nullptr);
+    ASSERT_EQ(data->parent(), entt::resolve("data"));
+    ASSERT_EQ(data->type(), entt::resolve<int>());
+    ASSERT_STREQ(data->name(), "j");
+    ASSERT_TRUE(data->is_const());
+    ASSERT_FALSE(data->is_static());
+    ASSERT_EQ(data->get(instance).cast<int>(), 1);
+    ASSERT_FALSE(data->set(instance, 42));
+    ASSERT_EQ(data->get(instance).cast<int>(), 1);
+
+    data->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_int);
+        ASSERT_EQ(prop->value(), 1);
+    });
+
+    ASSERT_EQ(data->prop(properties::prop_bool), nullptr);
+
+    auto *prop = data->prop(properties::prop_int);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_int);
+    ASSERT_EQ(prop->value(), 1);
+}
+
+TEST_F(Meta, MetaDataStatic) {
+    auto *data = entt::resolve<data_type>()->data("h");
+
+    ASSERT_NE(data, nullptr);
+    ASSERT_EQ(data->parent(), entt::resolve("data"));
+    ASSERT_EQ(data->type(), entt::resolve<int>());
+    ASSERT_STREQ(data->name(), "h");
+    ASSERT_FALSE(data->is_const());
+    ASSERT_TRUE(data->is_static());
+    ASSERT_EQ(data->get({}).cast<int>(), 2);
+    ASSERT_TRUE(data->set({}, 42));
+    ASSERT_EQ(data->get({}).cast<int>(), 42);
+
+    data->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_int);
+        ASSERT_EQ(prop->value(), 2);
+    });
+
+    ASSERT_EQ(data->prop(properties::prop_bool), nullptr);
+
+    auto *prop = data->prop(properties::prop_int);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_int);
+    ASSERT_EQ(prop->value(), 2);
+}
+
+TEST_F(Meta, MetaDataConstStatic) {
+    auto *data = entt::resolve<data_type>()->data("k");
+
+    ASSERT_NE(data, nullptr);
+    ASSERT_EQ(data->parent(), entt::resolve("data"));
+    ASSERT_EQ(data->type(), entt::resolve<int>());
+    ASSERT_STREQ(data->name(), "k");
+    ASSERT_TRUE(data->is_const());
+    ASSERT_TRUE(data->is_static());
+    ASSERT_EQ(data->get({}).cast<int>(), 3);
+    ASSERT_FALSE(data->set({}, 42));
+    ASSERT_EQ(data->get({}).cast<int>(), 3);
+
+    data->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_int);
+        ASSERT_EQ(prop->value(), 3);
+    });
+
+    ASSERT_EQ(data->prop(properties::prop_bool), nullptr);
+
+    auto *prop = data->prop(properties::prop_int);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_int);
+    ASSERT_EQ(prop->value(), 3);
+}
+
+TEST_F(Meta, MetaDataGetMetaAnyArg) {
+    auto *data = entt::resolve<data_type>()->data("i");
+    entt::meta_any any{data_type{}};
+    any.cast<data_type>().i = 99;
+    const auto value = data->get(any);
+
+    ASSERT_TRUE(value);
+    ASSERT_TRUE(value.can_cast<int>());
+    ASSERT_EQ(value.cast<int>(), 99);
+}
+
+TEST_F(Meta, MetaDataGetInvalidArg) {
+    auto *data = entt::resolve<data_type>()->data("i");
+    ASSERT_FALSE(data->get(0));
+}
+
+TEST_F(Meta, MetaDataSetMetaAnyArg) {
+    auto *data = entt::resolve<data_type>()->data("i");
+    entt::meta_any any{data_type{}};
+    entt::meta_any value{42};
+
+    ASSERT_EQ(any.cast<data_type>().i, 0);
+    ASSERT_TRUE(data->set(any, value));
+    ASSERT_EQ(any.cast<data_type>().i, 42);
+}
+
+TEST_F(Meta, MetaDataSetInvalidArg) {
+    auto *data = entt::resolve<data_type>()->data("i");
+    ASSERT_FALSE(data->set({}, 'c'));
+}
+
+TEST_F(Meta, MetaDataSetCast) {
+    auto *data = entt::resolve<data_type>()->data("empty");
+    data_type instance{};
+
+    ASSERT_EQ(empty_type::counter, 0);
+    ASSERT_TRUE(data->set(instance, fat_type{}));
+    ASSERT_EQ(empty_type::counter, 1);
+}
+
+TEST_F(Meta, MetaDataSetConvert) {
+    auto *data = entt::resolve<data_type>()->data("i");
+    data_type instance{};
+
+    ASSERT_EQ(instance.i, 0);
+    ASSERT_TRUE(data->set(instance, 3.));
+    ASSERT_EQ(instance.i, 3);
+}
+
+TEST_F(Meta, MetaFunc) {
+    auto *func = entt::resolve<func_type>()->func("f2");
+    func_type instance{};
+
+    ASSERT_NE(func, nullptr);
+    ASSERT_EQ(func->parent(), entt::resolve("func"));
+    ASSERT_STREQ(func->name(), "f2");
+    ASSERT_EQ(func->size(), entt::meta_func::size_type{2});
+    ASSERT_FALSE(func->is_const());
+    ASSERT_FALSE(func->is_static());
+    ASSERT_EQ(func->ret(), entt::resolve<int>());
+    ASSERT_EQ(func->arg(entt::meta_func::size_type{0}), entt::resolve<int>());
+    ASSERT_EQ(func->arg(entt::meta_func::size_type{1}), entt::resolve<int>());
+    ASSERT_EQ(func->arg(entt::meta_func::size_type{2}), nullptr);
+
+    auto any = func->invoke(instance, 3, 2);
+    auto empty = func->invoke(instance);
+
+    ASSERT_FALSE(empty);
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::resolve<int>());
+    ASSERT_EQ(any.cast<int>(), 4);
+    ASSERT_EQ(func_type::value, 3);
+
+    func->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_bool);
+        ASSERT_FALSE(prop->value().template cast<bool>());
+    });
+
+    ASSERT_EQ(func->prop(properties::prop_int), nullptr);
+
+    auto *prop = func->prop(properties::prop_bool);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_bool);
+    ASSERT_FALSE(prop->value().cast<bool>());
+}
+
+TEST_F(Meta, MetaFuncConst) {
+    auto *func = entt::resolve<func_type>()->func("f1");
+    func_type instance{};
+
+    ASSERT_NE(func, nullptr);
+    ASSERT_EQ(func->parent(), entt::resolve("func"));
+    ASSERT_STREQ(func->name(), "f1");
+    ASSERT_EQ(func->size(), entt::meta_func::size_type{1});
+    ASSERT_TRUE(func->is_const());
+    ASSERT_FALSE(func->is_static());
+    ASSERT_EQ(func->ret(), entt::resolve<int>());
+    ASSERT_EQ(func->arg(entt::meta_func::size_type{0}), entt::resolve<int>());
+    ASSERT_EQ(func->arg(entt::meta_func::size_type{1}), nullptr);
+
+    auto any = func->invoke(instance, 4);
+    auto empty = func->invoke(instance, 'c');
+
+    ASSERT_FALSE(empty);
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::resolve<int>());
+    ASSERT_EQ(any.cast<int>(), 16);
+
+    func->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_bool);
+        ASSERT_FALSE(prop->value().template cast<bool>());
+    });
+
+    ASSERT_EQ(func->prop(properties::prop_int), nullptr);
+
+    auto *prop = func->prop(properties::prop_bool);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_bool);
+    ASSERT_FALSE(prop->value().cast<bool>());
+}
+
+TEST_F(Meta, MetaFuncRetVoid) {
+    auto *func = entt::resolve<func_type>()->func("g");
+    func_type instance{};
+
+    ASSERT_NE(func, nullptr);
+    ASSERT_EQ(func->parent(), entt::resolve("func"));
+    ASSERT_STREQ(func->name(), "g");
+    ASSERT_EQ(func->size(), entt::meta_func::size_type{1});
+    ASSERT_FALSE(func->is_const());
+    ASSERT_FALSE(func->is_static());
+    ASSERT_EQ(func->ret(), entt::resolve<void>());
+    ASSERT_EQ(func->arg(entt::meta_func::size_type{0}), entt::resolve<int>());
+    ASSERT_EQ(func->arg(entt::meta_func::size_type{1}), nullptr);
+
+    auto any = func->invoke(instance, 5);
+
+    ASSERT_FALSE(any);
+    ASSERT_EQ(func_type::value, 25);
+
+    func->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_bool);
+        ASSERT_FALSE(prop->value().template cast<bool>());
+    });
+
+    ASSERT_EQ(func->prop(properties::prop_int), nullptr);
+
+    auto *prop = func->prop(properties::prop_bool);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_bool);
+    ASSERT_FALSE(prop->value().cast<bool>());
+}
+
+TEST_F(Meta, MetaFuncStatic) {
+    auto *func = entt::resolve<func_type>()->func("h");
+
+    ASSERT_NE(func, nullptr);
+    ASSERT_EQ(func->parent(), entt::resolve("func"));
+    ASSERT_STREQ(func->name(), "h");
+    ASSERT_EQ(func->size(), entt::meta_func::size_type{1});
+    ASSERT_FALSE(func->is_const());
+    ASSERT_TRUE(func->is_static());
+    ASSERT_EQ(func->ret(), entt::resolve<int>());
+    ASSERT_EQ(func->arg(entt::meta_func::size_type{0}), entt::resolve<int>());
+    ASSERT_EQ(func->arg(entt::meta_func::size_type{1}), nullptr);
+
+    auto any = func->invoke({}, 42);
+    auto empty = func->invoke({}, 'c');
+
+    ASSERT_FALSE(empty);
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::resolve<int>());
+    ASSERT_EQ(any.cast<int>(), 42);
+
+    func->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_bool);
+        ASSERT_FALSE(prop->value().template cast<bool>());
+    });
+
+    ASSERT_EQ(func->prop(properties::prop_int), nullptr);
+
+    auto *prop = func->prop(properties::prop_bool);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_bool);
+    ASSERT_FALSE(prop->value().cast<bool>());
+}
+
+TEST_F(Meta, MetaFuncStaticRetVoid) {
+    auto *func = entt::resolve<func_type>()->func("k");
+
+    ASSERT_NE(func, nullptr);
+    ASSERT_EQ(func->parent(), entt::resolve("func"));
+    ASSERT_STREQ(func->name(), "k");
+    ASSERT_EQ(func->size(), entt::meta_func::size_type{1});
+    ASSERT_FALSE(func->is_const());
+    ASSERT_TRUE(func->is_static());
+    ASSERT_EQ(func->ret(), entt::resolve<void>());
+    ASSERT_EQ(func->arg(entt::meta_func::size_type{0}), entt::resolve<int>());
+    ASSERT_EQ(func->arg(entt::meta_func::size_type{1}), nullptr);
+
+    auto any = func->invoke({}, 42);
+
+    ASSERT_FALSE(any);
+    ASSERT_EQ(func_type::value, 42);
+
+    func->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_bool);
+        ASSERT_FALSE(prop->value().template cast<bool>());
+    });
+
+    ASSERT_EQ(func->prop(properties::prop_int), nullptr);
+
+    auto *prop = func->prop(properties::prop_bool);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_bool);
+    ASSERT_FALSE(prop->value().cast<bool>());
+}
+
+TEST_F(Meta, MetaFuncMetaAnyArgs) {
+    auto *func = entt::resolve<func_type>()->func("f1");
+    auto any = func->invoke(func_type{}, entt::meta_any{3});
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::resolve<int>());
+    ASSERT_EQ(any.cast<int>(), 9);
+}
+
+TEST_F(Meta, MetaFuncInvalidArgs) {
+    auto *func = entt::resolve<func_type>()->func("f1");
+    ASSERT_FALSE(func->invoke(empty_type{}, entt::meta_any{'c'}));
+}
+
+TEST_F(Meta, MetaFuncCastAndConvert) {
+    auto *func = entt::resolve<func_type>()->func("f3");
+    auto any = func->invoke(func_type{}, derived_type{}, 0, 3.);
+
+    ASSERT_TRUE(any);
+    ASSERT_EQ(any.type(), entt::resolve<int>());
+    ASSERT_EQ(any.cast<int>(), 9);
+}
+
+TEST_F(Meta, MetaType) {
+    auto *type = entt::resolve<derived_type>();
+
+    ASSERT_NE(type, nullptr);
+    ASSERT_STREQ(type->name(), "derived");
+
+    type->prop([](auto *prop) {
+        ASSERT_NE(prop, nullptr);
+        ASSERT_EQ(prop->key(), properties::prop_int);
+        ASSERT_EQ(prop->value(), 99);
+    });
+
+    ASSERT_EQ(type->prop(properties::prop_bool), nullptr);
+
+    auto *prop = type->prop(properties::prop_int);
+
+    ASSERT_NE(prop, nullptr);
+    ASSERT_EQ(prop->key(), properties::prop_int);
+    ASSERT_EQ(prop->value(), 99);
+}
+
+TEST_F(Meta, MetaTypeTraits) {
+    ASSERT_TRUE(entt::resolve<void>()->is_void());
+    ASSERT_TRUE(entt::resolve<properties>()->is_enum());
+    ASSERT_TRUE(entt::resolve<derived_type>()->is_class());
+    ASSERT_TRUE(entt::resolve<int *>()->is_pointer());
+    ASSERT_TRUE(entt::resolve<decltype(&empty_type::destroy)>()->is_function_pointer());
+    ASSERT_TRUE(entt::resolve<decltype(&data_type::i)>()->is_member_object_pointer());
+    ASSERT_TRUE(entt::resolve<decltype(&func_type::g)>()->is_member_function_pointer());
+    ASSERT_TRUE(entt::resolve<decltype(&data_type::j)>()->is_member_pointer());
+    ASSERT_TRUE(entt::resolve<bool>()->is_arithmetic());
+    ASSERT_TRUE(entt::resolve<properties>()->is_compound());
+}
+
+TEST_F(Meta, MetaTypeBase) {
+    auto *type = entt::resolve<derived_type>();
+    bool iterate = false;
+
+    type->base([&iterate](auto *base) {
+        ASSERT_EQ(base->type(), entt::resolve<base_type>());
+        iterate = true;
+    });
+
+    ASSERT_TRUE(iterate);
+    ASSERT_EQ(type->base("base")->type(), entt::resolve<base_type>());
+}
+
+TEST_F(Meta, MetaTypeConv) {
+    auto *type = entt::resolve<double>();
+    bool iterate = false;
+
+    type->conv([&iterate](auto *conv) {
+        ASSERT_EQ(conv->type(), entt::resolve<int>());
+        iterate = true;
+    });
+
+    ASSERT_TRUE(iterate);
+
+    auto *conv = type->conv<int>();
+
+    ASSERT_EQ(conv->type(), entt::resolve<int>());
+    ASSERT_EQ(type->conv<char>(), nullptr);
+}
+
+TEST_F(Meta, MetaTypeCtor) {
+    auto *type = entt::resolve<derived_type>();
+    int counter{};
+
+    type->ctor([&counter](auto *) {
+        ++counter;
+    });
+
+    ASSERT_EQ(counter, 2);
+    ASSERT_NE((type->ctor<const base_type &, int>()), nullptr);
+    ASSERT_NE((type->ctor<const derived_type &, double>()), nullptr);
+}
+
+TEST_F(Meta, MetaTypeDtor) {
+    ASSERT_NE(entt::resolve<fat_type>()->dtor(), nullptr);
+    ASSERT_EQ(entt::resolve<int>()->dtor(), nullptr);
+}
+
+TEST_F(Meta, MetaTypeData) {
+    auto *type = entt::resolve<data_type>();
+    int counter{};
+
+    type->data([&counter](auto *) {
+        ++counter;
+    });
+
+    ASSERT_EQ(counter, 5);
+    ASSERT_NE(type->data("i"), nullptr);
+}
+
+TEST_F(Meta, MetaTypeFunc) {
+    auto *type = entt::resolve<func_type>();
+    int counter{};
+
+    type->func([&counter](auto *) {
+        ++counter;
+    });
+
+    ASSERT_EQ(counter, 6);
+    ASSERT_NE(type->func("f1"), nullptr);
+}
+
+TEST_F(Meta, MetaTypeConstruct) {
+    auto *type = entt::resolve<derived_type>();
+    auto any = type->construct(base_type{}, 42, 'c');
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(any.can_cast<derived_type>());
+    ASSERT_EQ(any.cast<derived_type>().i, 42);
+    ASSERT_EQ(any.cast<derived_type>().c, 'c');
+}
+
+TEST_F(Meta, MetaTypeConstructMetaAnyArgs) {
+    auto *type = entt::resolve<derived_type>();
+    auto any = type->construct(entt::meta_any{base_type{}}, entt::meta_any{42}, entt::meta_any{'c'});
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(any.can_cast<derived_type>());
+    ASSERT_EQ(any.cast<derived_type>().i, 42);
+    ASSERT_EQ(any.cast<derived_type>().c, 'c');
+}
+
+
+TEST_F(Meta, MetaTypeConstructInvalidArgs) {
+    auto *type = entt::resolve<derived_type>();
+    auto any = type->construct(entt::meta_any{base_type{}}, entt::meta_any{'c'}, entt::meta_any{42});
+    ASSERT_FALSE(any);
+}
+
+
+TEST_F(Meta, MetaTypeConstructCastAndConvert) {
+    auto *type = entt::resolve<derived_type>();
+    auto any = type->construct(entt::meta_any{derived_type{}}, entt::meta_any{42.}, entt::meta_any{'c'});
+
+    ASSERT_TRUE(any);
+    ASSERT_TRUE(any.can_cast<derived_type>());
+    ASSERT_EQ(any.cast<derived_type>().i, 42);
+    ASSERT_EQ(any.cast<derived_type>().c, 'c');
+}
+
+
+TEST_F(Meta, MetaTypeDestroyDtor) {
+    auto *type = entt::resolve<empty_type>();
+
+    ASSERT_EQ(empty_type::counter, 0);
+    ASSERT_TRUE(type->destroy(empty_type{}));
+    ASSERT_EQ(empty_type::counter, 1);
+}
+
+TEST_F(Meta, MetaTypeDestroyDtorInvalidArg) {
+    auto *type = entt::resolve<empty_type>();
+
+    ASSERT_EQ(empty_type::counter, 0);
+    ASSERT_FALSE(type->destroy('c'));
+    ASSERT_EQ(empty_type::counter, 0);
+}
+
+TEST_F(Meta, MetaTypeDestroyDtorCastAndConvert) {
+    auto *type = entt::resolve<empty_type>();
+
+    ASSERT_EQ(empty_type::counter, 0);
+    ASSERT_FALSE(type->destroy(fat_type{}));
+    ASSERT_EQ(empty_type::counter, 0);
+    ASSERT_FALSE(entt::resolve<int>()->destroy(42.));
+}
+
+TEST_F(Meta, MetaTypeDestroyNoDtor) {
+    auto *type = entt::resolve<char>();
+    ASSERT_TRUE(type->destroy('c'));
+}
+
+TEST_F(Meta, MetaTypeDestroyNoDtorInvalidArg) {
+    auto *type = entt::resolve<char>();
+    ASSERT_FALSE(type->destroy(42));
+}
+
+TEST_F(Meta, MetaTypeDestroyNoDtorVoid) {
+    auto *type = entt::resolve<void>();
+    ASSERT_FALSE(type->destroy({}));
+}
+
+TEST_F(Meta, MetaTypeDestroyNoDtorCastAndConvert) {
+    auto *type = entt::resolve<int>();
+    ASSERT_FALSE(type->destroy(42.));
+}
+
+TEST_F(Meta, MetaDataFromBase) {
+    auto *type = entt::resolve<concrete_type>();
+    concrete_type instance;
+
+    ASSERT_NE(type->data("i"), nullptr);
+    ASSERT_NE(type->data("j"), nullptr);
+
+    ASSERT_EQ(instance.i, 0);
+    ASSERT_EQ(instance.j, char{});
+    ASSERT_TRUE(type->data("i")->set(instance, 3));
+    ASSERT_TRUE(type->data("j")->set(instance, 'c'));
+    ASSERT_EQ(instance.i, 3);
+    ASSERT_EQ(instance.j, 'c');
+}
+
+TEST_F(Meta, MetaFuncFromBase) {
+    auto *type = entt::resolve<concrete_type>();
+    auto *base = entt::resolve<an_abstract_type>();
+    concrete_type instance;
+
+    ASSERT_NE(type->func("f"), nullptr);
+    ASSERT_NE(type->func("g"), nullptr);
+    ASSERT_NE(type->func("h"), nullptr);
+
+    ASSERT_EQ(type->func("f")->parent(), entt::resolve<concrete_type>());
+    ASSERT_EQ(type->func("g")->parent(), entt::resolve<an_abstract_type>());
+    ASSERT_EQ(type->func("h")->parent(), entt::resolve<another_abstract_type>());
+
+    ASSERT_EQ(instance.i, 0);
+    ASSERT_EQ(instance.j, char{});
+
+    type->func("f")->invoke(instance, 3);
+    type->func("h")->invoke(instance, 'c');
+
+    ASSERT_EQ(instance.i, 9);
+    ASSERT_EQ(instance.j, 'c');
+
+    base->func("g")->invoke(instance, 3);
+
+    ASSERT_EQ(instance.i, -3);
+}
+
+TEST_F(Meta, MetaPropFromBase) {
+    auto *type = entt::resolve<concrete_type>();
+    auto *prop_bool = type->prop(properties::prop_bool);
+    auto *prop_int = type->prop(properties::prop_int);
+
+    ASSERT_NE(prop_bool, nullptr);
+    ASSERT_NE(prop_int, nullptr);
+
+    ASSERT_FALSE(prop_bool->value().cast<bool>());
+    ASSERT_EQ(prop_int->value().cast<int>(), 42);
+}
+
+TEST_F(Meta, AbstractClass) {
+    auto *type = entt::resolve<an_abstract_type>();
+    concrete_type instance;
+
+    ASSERT_EQ(instance.i, 0);
+
+    type->func("f")->invoke(instance, 3);
+
+    ASSERT_EQ(instance.i, 3);
+
+    type->func("g")->invoke(instance, 3);
+
+    ASSERT_EQ(instance.i, -3);
+}