diff --git a/docs/md/meta.md b/docs/md/meta.md
index 5a744b9cd..8c8a2e842 100644
--- a/docs/md/meta.md
+++ b/docs/md/meta.md
@@ -15,6 +15,7 @@
* [Template information](#template-information)
* [Automatic conversions](#automatic-conversions)
* [Implicitly generated default constructor](#implicitly-generated-default-constructor)
+ * [From void to any](#from-void-to-any)
* [Policies: the more, the less](#policies-the-more-the-less)
* [Named constants and enums](#named-constants-and-enums)
* [Properties and meta objects](#properties-and-meta-objects)
@@ -783,6 +784,24 @@ useful for building keys without knowing or having to register the actual types.
In all cases, when users register default constructors, they are preferred both
during searches and when the `construct` member function is invoked.
+## From void to any
+
+Sometimes all a user has is an opaque pointer to an object of a known meta type.
+It would be handy in this case to be able to construct a `meta_any` object from
+them.
+For this purpose, the `meta_type` class offers a `from_void` member function
+designed to convert an opaque pointer into a `meta_any`:
+
+```cpp
+entt::meta_any any = entt::resolve(id).from_void(element);
+```
+
+It goes without saying that it's not possible to do a check on the actual type.
+Therefore, this call can be considered as a _static cast_ with all the problems
+and undefined behaviors of the case following errors.
+On the other hand, the ability to construct a `meta_any` from an opaque pointer
+opens the door to some pretty interesting uses that are worth exploring.
+
## Policies: the more, the less
Policies are a kind of compile-time directives that can be used when registering
diff --git a/src/entt/meta/meta.hpp b/src/entt/meta/meta.hpp
index ba2d5d2d9..881124c53 100644
--- a/src/entt/meta/meta.hpp
+++ b/src/entt/meta/meta.hpp
@@ -1231,6 +1231,20 @@ public:
return construct(arguments, sizeof...(Args));
}
+ /**
+ * @brief Wraps an opaque element of the underlying type.
+ * @param element A valid pointer to an element of the underlying type.
+ * @return A wrapper that references the given instance.
+ */
+ meta_any from_void(void *element) {
+ return node->from_void ? node->from_void(element, nullptr) : meta_any{};
+ }
+
+ /*! @copydoc from_void */
+ meta_any from_void(const void *element) {
+ return node->from_void ? node->from_void(nullptr, element) : meta_any{};
+ }
+
/**
* @brief Invokes a function given an identifier, if possible.
*
diff --git a/src/entt/meta/node.hpp b/src/entt/meta/node.hpp
index 8ab87034f..c29856178 100644
--- a/src/entt/meta/node.hpp
+++ b/src/entt/meta/node.hpp
@@ -111,6 +111,7 @@ struct meta_type_node {
meta_type_node *(*const remove_pointer)() noexcept;
meta_any (*const default_constructor)();
double (*const conversion_helper)(void *, const void *);
+ meta_any (*const from_void)(void *, const void *);
const meta_template_node *const templ;
meta_ctor_node *ctor{nullptr};
meta_base_node *base{nullptr};
@@ -149,6 +150,16 @@ class ENTT_API meta_node {
}
}
+ [[nodiscard]] static auto *meta_from_void() noexcept {
+ if constexpr(std::is_same_v || std::is_array_v || std::is_function_v) {
+ return static_cast>(nullptr);
+ } else {
+ return +[](void *element, const void *as_const) {
+ return element ? meta_any{std::in_place_type, *static_cast(element)} : meta_any{std::in_place_type, *static_cast(as_const)};
+ };
+ }
+ }
+
[[nodiscard]] static meta_template_node *meta_template_info() noexcept {
if constexpr(is_complete_v>) {
static meta_template_node node{
@@ -184,6 +195,7 @@ public:
&meta_node>>::resolve,
meta_default_constructor(),
meta_conversion_helper(),
+ meta_from_void(),
meta_template_info()
// tricks clang-format
};
diff --git a/test/entt/meta/meta_type.cpp b/test/entt/meta/meta_type.cpp
index 6b77b7f1e..5cc29fc46 100644
--- a/test/entt/meta/meta_type.cpp
+++ b/test/entt/meta/meta_type.cpp
@@ -465,6 +465,34 @@ TEST_F(MetaType, ConstructArithmeticConversion) {
ASSERT_EQ(any.cast().value, 1);
}
+TEST_F(MetaType, FromVoid) {
+ using namespace entt::literals;
+
+ ASSERT_FALSE(entt::resolve().from_void(static_cast(nullptr)));
+ ASSERT_FALSE(entt::resolve().from_void(static_cast(nullptr)));
+
+ auto type = entt::resolve();
+ double value = 4.2;
+
+ auto as_void = type.from_void(static_cast(&value));
+ auto as_const_void = type.from_void(static_cast(&value));
+
+ ASSERT_TRUE(as_void);
+ ASSERT_TRUE(as_const_void);
+
+ ASSERT_EQ(as_void.type(), entt::resolve());
+ ASSERT_NE(as_void.try_cast(), nullptr);
+
+ ASSERT_EQ(as_const_void.type(), entt::resolve());
+ ASSERT_EQ(as_const_void.try_cast(), nullptr);
+ ASSERT_NE(as_const_void.try_cast(), nullptr);
+
+ value = 1.2;
+
+ ASSERT_EQ(as_void.cast(), as_const_void.cast());
+ ASSERT_EQ(as_void.cast(), 1.2);
+}
+
TEST_F(MetaType, Reset) {
using namespace entt::literals;