meta/*: removed meta_storage, meta_any uses any internally

This commit is contained in:
Michele Caini
2020-11-30 09:43:51 +01:00
parent c95041d7c3
commit 5af7e96f70
3 changed files with 30 additions and 221 deletions

View File

@@ -8,7 +8,7 @@
* [Introduction](#introduction)
* [Names and identifiers](#names-and-identifiers)
* [Reflection in a nutshell](#reflection-in-a-nutshell)
* [Any as in any type](#any-as-in-any-type)
* [Any to the rescue](#any-to-the-rescue)
* [Enjoy the runtime](#enjoy-the-runtime)
* [Container support](#container-support)
* [Pointer-like types](#pointer-like-types)
@@ -192,75 +192,44 @@ 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
## Any to the rescue
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.
The reflection system offers a kind of _extended version_ of the `any` class
(see the core module for more details).<br/>
The purpose is to add some feature on top of those already present, so as to
integrate it with the meta type system without having to duplicate the code.
The class `meta_any` offers 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 through the reflection system, allocations are at zero.
Creating instances of `meta_any`, whether empty or from existing objects, is
trivial:
The API is very similar to that of the `any` type. The class `meta_any` _wraps_
many of the feature to infer a meta node, before forwarding some or all of the
arguments to the underlying storage.<br/>
Among the few relevant differences, instances of `meta_any` are comparable,
while those of `any` are not:
```cpp
// a container for an int
entt::meta_any any{0};
entt::meta_any any{42};
entt::meta_any other{'c'};
// an empty container
entt::meta_any empty{};
const bool equal = (any == other);
```
The `meta_any` class takes also the burden of destroying the contained object
when required.<br/>
Furthermore, an instance of `meta_any` is not tied to a specific type.
Therefore, the wrapper will be reconfigured by assigning it an object of a
different type than the one contained, so as to be able to handle the new
instance.
A particularly interesting feature of this class is that it can also be used as
an opaque container for non-const unmanaged objects:
Also, `meta_any` adds support for containers and pointer-like types (see the
following sections for more details).<br/>
Similar to `any`, this class can also be used to create _aliases_ for unmanaged
objects. However, unlike `any`,` meta_any` treats an empty instance and one
initialized with `void` differently:
```cpp
int value;
entt::meta_any any{std::ref(value)};
entt::meta_any any{};
entt::meta_any other{std::in_place_type<void>};
```
In other words, whenever `meta_any` intercepts a `reference_wrapper`, it acts as
a reference to the original instance rather than making a copy of it. The
contained object is never destroyed and users must ensure that its lifetime
exceeds that of the container.<br/>
Similarly, it's possible to create non-owning copies of `meta_any` from existing
ones:
```cpp
// aliasing constructor
entt::meta_any ref = any.ref();
```
In this case, it doesn't matter if the starting container actually holds an
object or acts already as a reference for unmanaged elements, the new instance
thus created won't create copies and will only serve as a reference for the
original item.<br/>
It means that, starting from the example above, both `ref` and` any` will point
to the same object, whether it's initially contained in `any` or already an
unmanaged one. This is particularly useful for passing instances of `meta_any`
belonging to the external context by reference to a function or a constructor
rather than making copies of them.
The `meta_any` class also has a `type` member function that returns the meta
type of the contained value, if any. The member functions `try_cast`, `cast` and
`convert` are then used to know if the underlying object has a given type as a
base or if it can be converted implicitly to it.
While `any` treats both objects as empty, `meta_any` treats objects initialized
with `void` as if they were _valid_ ones. This allows to differentiate between
failed function calls and function calls that are successful but return
nothing.<br/>
Finally, the member functions `try_cast`, `cast` and `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. There is in fact no `any_cast` equivalent for `meta_any`.
## Enjoy the runtime

View File

@@ -31,167 +31,6 @@ struct meta_handle;
namespace internal {
class meta_storage {
enum class operation { COPY, MOVE, DTOR, ADDR, REF };
using storage_type = std::aligned_storage_t<sizeof(double[2]), alignof(double[2])>;
using vtable_type = void *(const operation, const meta_storage &, meta_storage *);
template<typename Type>
static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
template<typename Type>
static void * basic_vtable(const operation op, const meta_storage &from, meta_storage *to) {
if constexpr(std::is_void_v<Type>) {
return nullptr;
} else if constexpr(std::is_lvalue_reference_v<Type>) {
switch(op) {
case operation::REF:
to->vtable = from.vtable;
[[fallthrough]];
case operation::COPY:
case operation::MOVE:
to->instance = from.instance;
break;
case operation::ADDR:
return from.instance;
case operation::DTOR:
break;
}
} else if constexpr(in_situ<Type>) {
auto *instance = std::launder(reinterpret_cast<Type *>(&const_cast<storage_type &>(from.storage)));
switch(op) {
case operation::COPY:
new (&to->storage) Type{std::as_const(*instance)};
break;
case operation::MOVE:
new (&to->storage) Type{std::move(*instance)};
[[fallthrough]];
case operation::DTOR:
instance->~Type();
break;
case operation::ADDR:
return instance;
case operation::REF:
to->vtable = basic_vtable<std::add_lvalue_reference_t<Type>>;
to->instance = instance;
break;
}
} else {
switch(op) {
case operation::COPY:
to->instance = new Type{std::as_const(*static_cast<Type *>(from.instance))};
break;
case operation::MOVE:
to->instance = from.instance;
break;
case operation::DTOR:
delete static_cast<Type *>(from.instance);
break;
case operation::ADDR:
return from.instance;
case operation::REF:
to->vtable = basic_vtable<std::add_lvalue_reference_t<Type>>;
to->instance = from.instance;
break;
}
}
return nullptr;
}
public:
meta_storage() ENTT_NOEXCEPT
: vtable{&basic_vtable<void>},
instance{}
{}
template<typename Type, typename... Args>
explicit meta_storage(std::in_place_type_t<Type>, [[maybe_unused]] Args &&... args)
: vtable{&basic_vtable<Type>},
instance{}
{
if constexpr(!std::is_void_v<Type>) {
if constexpr(in_situ<Type>) {
new (&storage) Type{std::forward<Args>(args)...};
} else {
instance = new Type{std::forward<Args>(args)...};
}
}
}
template<typename Type>
meta_storage(std::reference_wrapper<Type> value)
: vtable{&basic_vtable<std::add_lvalue_reference_t<Type>>},
instance{&value.get()}
{}
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, meta_storage>>>
meta_storage(Type &&value)
: meta_storage{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)}
{}
meta_storage(const meta_storage &other)
: meta_storage{}
{
vtable = other.vtable;
vtable(operation::COPY, other, this);
}
meta_storage(meta_storage &&other) ENTT_NOEXCEPT
: meta_storage{}
{
vtable = std::exchange(other.vtable, &basic_vtable<void>);
vtable(operation::MOVE, other, this);
}
~meta_storage() {
vtable(operation::DTOR, *this, nullptr);
}
meta_storage & operator=(meta_storage other) {
swap(*this, other);
return *this;
}
[[nodiscard]] const void * data() const ENTT_NOEXCEPT {
return vtable(operation::ADDR, *this, nullptr);
}
[[nodiscard]] void * data() ENTT_NOEXCEPT {
return vtable(operation::ADDR, *this, nullptr);
}
template<typename Type, typename... Args>
void emplace(Args &&... args) {
*this = meta_storage{std::in_place_type<Type>, std::forward<Args>(args)...};
}
[[nodiscard]] meta_storage ref() const ENTT_NOEXCEPT {
meta_storage other{};
vtable(operation::REF, *this, &other);
return other;
}
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return !(vtable(operation::ADDR, *this, nullptr) == nullptr);
}
friend void swap(meta_storage &lhs, meta_storage &rhs) {
meta_storage tmp{};
lhs.vtable(operation::MOVE, lhs, &tmp);
rhs.vtable(operation::MOVE, rhs, &lhs);
lhs.vtable(operation::MOVE, tmp, &rhs);
std::swap(lhs.vtable, rhs.vtable);
}
private:
vtable_type *vtable;
union { void *instance; storage_type storage; };
};
struct meta_type_node;

View File

@@ -10,6 +10,7 @@
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/any.hpp"
#include "../core/fwd.hpp"
#include "../core/utility.hpp"
#include "../core/type_info.hpp"
@@ -506,7 +507,7 @@ public:
}
private:
internal::meta_storage storage;
any storage;
internal::meta_type_node *node;
dereference_operator_type *deref;
meta_sequence_container(* seq_factory)(void *);