diff --git a/TODO b/TODO
index 41d1d8b3e..8fb2369c8 100644
--- a/TODO
+++ b/TODO
@@ -25,11 +25,7 @@ Next:
- update documentation to describe alternatives
* WIP:
- - meta: update doc
- static constexpr -> inline constexpr
- remove internal::find_if
- add const meta container support to meta any
- - meta_any deref fails if operator* returns a temporary
- - remove dereferenceable detector from type traits (is broken)
- - update meta.md
- review _t
diff --git a/docs/md/meta.md b/docs/md/meta.md
index 9716ebed0..9b6032509 100644
--- a/docs/md/meta.md
+++ b/docs/md/meta.md
@@ -11,6 +11,7 @@
* [Any as in any type](#any-as-in-any-type)
* [Enjoy the runtime](#enjoy-the-runtime)
* [Container support](#container-support)
+ * [Pointer-like types](#pointer-like-types)
* [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)
@@ -407,52 +408,62 @@ read the inline documentation to get the best out of this powerful tool.
## Container support
-The meta module offers minimal support for containers of all types.
-Here _containers_ doesn't necessarily mean those offered by the C++ standard
-library. As long as the user defined containers are subject to the following
-rules, they will be automatically intercepted by the reflection system:
+The meta module supports containers of all types out of the box.
+Moreover, _containers_ doesn't necessarily mean those offered by the C++
+standard library. In fact, user defined data structures can also work with the
+meta system in many cases.
-* A type is generally considered a container if the `begin`/`end` functions
- exist and are resolvable through unqualified lookup.
+To make a container be recognized by the meta module, users are required to
+provide specializations for either the `meta_sequence_container_traits` class or
+the `meta_associative_container_traits` class, according with the actual _type_
+of the container.
+`EnTT` already exports the specializations for some common classes. In
+particular:
-* A type is considered an associative container if it's a container **and** it
- has a public alias declaration called `key_type`.
+* `std::vector` and `std::array` are exported as _sequence containers_.
+* `std::map`, `std::set` and their unordered counterparts are exported as
+ _associative containers_.
-* A type is considered a key-only associative container if it's an associative
- container **and** the public alias declarations `key_type` and `value_type`
- refer to the same type.
+It's important to include the header file `container.hpp` to make these
+specializations available to the compiler when needed.
+The same file also contains many examples for the users that are interested in
+making their own containers available to the meta system.
-* A type is considered a sequence container if it's a container and it's not an
- associative container.
-
-* A type is considered a dynamic sequence container if it's a sequence container
- **and** it offers an `insert` member function that accepts an iterator and a
- value to insert.
-
-In addition to the above points, a container must exhibit a set of features that
-comply with the standard library guidelines. For example, it must offer a `size`
-method and a public alias declaration called `value_type`.
-
-Proxy objects for containers are returned by the `view` member function of the
-`meta_any` class:
+When a specialization of the `meta_sequence_container_traits` class exists, the
+meta system treats the wrapped type as a sequence container. In a similar way,
+a type is treated as an associative container if a specialization of the
+`meta_associative_container_traits` class is found for it.
+Proxy objects are returned by dedicated members of the `meta_any` class. The
+following is a deliberately verbose example of how users can access a proxy
+object for a sequence container:
```cpp
std::vector vec{1, 2, 3};
entt::meta_any any{std::ref(vec)};
-entt::meta_container view = any.view();
+
+if(any.type().is_sequence_container()) {
+ if(auto view = any.as_sequence_container(); view) {
+ // ...
+ }
+}
```
-Users aren't expected to _reflect_ containers explicitly. It's sufficient to
-assign a container to a `meta_any` object to be able to get its proxy. A proxy
-is also contextually convertible to bool to know if it's valid. For example,
-invalid proxies are returned when the wrapped object isn't a container.
-To find out if an instance of `meta_any` contains a container or not, simply
-query the associated meta type. The latter exposes some methods such as for
-example `is_sequence_container` and `is_associative_container` which allow users
-to _explore_ the underlying type.
+The method to use to get a proxy object for associative containers is
+`as_associative_container` instead.
+It goes without saying that it's not necessary to perform a double check.
+Instead, it's sufficient to query the meta type or verify that the proxy object
+is valid. In fact, proxies are contextually convertible to bool to know if they
+are valid. For example, invalid proxies are returned when the wrapped object
+isn't a container.
+In all cases, users aren't expected to _reflect_ containers explicitly. It's
+sufficient to assign a container for which a specialization of the traits
+classes exists to a `meta_any` object to be able to get its proxy object.
-All proxy objects offer the same interface, although the available features
-differ from case to case. In particular:
+The interface of the `meta_sequence_container` proxy object is the same for all
+types of sequence containers, although the available features differ from case
+to case. In particular:
+
+* The `value_type` member function returns the meta type of the elements.
* The `size` member function returns the number of elements in the container as
an unsigned integer value:
@@ -461,6 +472,115 @@ differ from case to case. In particular:
const auto size = view.size();
```
+* The `resize` member function allows to resize the wrapped container and
+ returns true in case of succes:
+
+ ```cpp
+ const bool ok = view.resize(3u);
+ ```
+
+ For example, it's not possible to resize fixed size containers.
+
+* The `clear` member function allows to clear the wrapped container and returns
+ true in case of success:
+
+ ```cpp
+ const bool ok = view.clear();
+ ```
+
+ For example, it's not possible to clear fixed size containers.
+
+* The `begin` and `end` member functions return opaque iterators that can be
+ used to iterate the container directly:
+
+ ```cpp
+ for(entt::meta_any element: view) {
+ // ...
+ }
+ ```
+
+ In all cases, given an underlying container of type `C`, the returned element
+ contains an object of type `C::value_type` which therefore depends on the
+ actual container.
+ All meta iterators are input iterators and don't offer an indirection operator
+ on purpose.
+
+* The `insert` member function can be used to add elements to the container. It
+ accepts a meta iterator and the element to insert:
+
+ ```cpp
+ auto last = view.end();
+ // appends an integer to the container
+ view.insert(last.handle(), 42);
+ ```
+
+ This function returns a meta iterator pointing to the inserted element and a
+ boolean value to indicate whether the operation was successful or not. Note
+ that a call to `insert` may silently fail in case of fixed size containers or
+ whether the arguments aren't at least convertible to the required types.
+ Since the meta iterators are contextually convertible to bool, users can rely
+ on them to know if the operation has failed on the actual container or
+ upstream, for example for an argument conversion problem.
+
+* The `erase` member function can be used to remove elements from the container.
+ It accepts a meta iterator to the element to remove:
+
+ ```cpp
+ auto first = view.begin();
+ // removes the first element from the container
+ view.erase(first);
+ ```
+
+ This function returns a meta iterator following the last removed element and a
+ boolean value to indicate whether the operation was successful or not. Note
+ that a call to `erase` may silently fail in case of fixed size containers.
+
+* The `operator[]` can be used to access elements in a container. It accepts a
+ single argument, that is the position of the element to return:
+
+ ```cpp
+ for(std::size_t pos{}, last = view.size(); pos < last; ++pos) {
+ entt::meta_any value = view[pos];
+ // ...
+ }
+ ```
+
+ The function returns instances of `meta_any` that directly refer to the actual
+ elements. Modifying the returned object will then directly modify the element
+ inside the container.
+
+Similarly, also the interface of the `meta_associative_container` proxy object
+is the same for all types of associative containers. However, there are some
+differences in behavior in the case of key-only containers. In particular:
+
+* The `key_only` member function returns true if the wrapped container is a
+ key-only one.
+
+* The `key_type` member function returns the meta type of the keys.
+
+* The `mapped_type` member function returns an invalid meta type for key-only
+ containers and the meta type of the mapped values for all other types of
+ containers.
+
+* The `value_type` member function returns the meta type of the elements.
+ For example, it returns the meta type of `int` for `std::set` while it
+ returns the meta type of `std::pair` for
+ `std::map`.
+
+* The `size` member function returns the number of elements in the container as
+ an unsigned integer value:
+
+ ```cpp
+ const auto size = view.size();
+ ```
+
+* The `clear` member function allows to clear the wrapped container and returns
+ true in case of success:
+
+ ```cpp
+ const bool ok = view.clear();
+ ```
+
* The `begin` and `end` member functions return opaque iterators that can be
used to iterate the container directly:
@@ -477,80 +597,85 @@ differ from case to case. In particular:
on purpose.
* The `insert` member function can be used to add elements to the container. It
- accepts a couple of erased objects that take on different meaning depending on
- the type of container.
- In case of sequence containers, the first argument is a handle to the actual
- iterator before which the element will be inserted while the second argument
- is the element to insert:
+ accepts two arguments, respectively the key and the value to be inserted:
```cpp
auto last = view.end();
// appends an integer to the container
- view.insert(last.handle(), 42);
- ```
-
- As for associative containers instead, the two arguments are respectively the
- key and the value to be inserted:
-
- ```cpp
- view.insert("key"_hs, "value");
+ view.insert(last.handle(), 42, 'c');
```
This function returns a boolean value to indicate whether the operation was
- successful or not. Note that a call to `insert` may silently fail in case of
- fixed size containers or whether the arguments aren't at least convertible to
- the required types.
+ successful or not. Note that a call to `insert` may fail when the arguments
+ aren't at least convertible to the required types.
* The `erase` member function can be used to remove elements from the container.
- It accepts a single argument that takes on different meaning depending on the
- type of container.
- In case of sequence containers, the argument is a handle to the actual
- iterator to the element to remove:
+ It accepts a single argument, that is the key to be removed:
```cpp
- auto first = view.begin();
- // removes the first element from the container
- view.erase(first);
- ```
-
- As for associative containers instead, the argument is the key to be removed:
-
- ```cpp
- view.erase("key"_hs);
+ view.erase(42);
```
This function returns a boolean value to indicate whether the operation was
- successful or not. Note that a call to `erase` may silently fail in case of
- fixed size containers or whether the argument isn't at least convertible to
- the required type.
+ successful or not. Note that a call to `erase` may fail when the argument
+ isn't at least convertible to the required type.
* The `operator[]` can be used to access elements in a container. It accepts a
- single argument that takes on different meaning depending on the type of
- container.
- In case of sequence containers, the argument is the position of the element to
- return:
+ single argument, that is the key of the element to return:
```cpp
- for(std::size_t pos{}, last = view.size(); pos < last; ++pos) {
- entt::meta_any value = view[pos];
- // ...
- }
+ entt::meta_any value = view[42];
```
- As for associative containers instead, the argument is the key of the element
- to return:
-
- ```cpp
- entt::meta_any value = view["key"_hs];
- ```
-
- In both cases, the function returns an instance of `meta_any` that directly
- refers to the actual element. This object may be invalid, for example when
- the argument isn't at least convertible to the required type.
+ The function returns instances of `meta_any` that directly refer to the actual
+ elements. Modifying the returned object will then directly modify the element
+ inside the container.
Container support is deliberately minimal but theoretically sufficient to
satisfy all needs.
+## Pointer-like types
+
+As with containers, it's also possible to communicate to the meta system which
+types to consider _pointers_. This will allow to dereference instances of
+`meta_any`, obtaining light _references_ to the pointed objects that are also
+correctly associated with their meta types.
+To make the meta system recognize a type as _pointer-like_, users can specialize
+the `is_meta_pointer_like` class. `EnTT` already exports the specializations for
+some common classes. In particular:
+
+* All types of raw pointers.
+* `std::uniqe_ptr` and `std::shared_ptr`.
+
+It's important to include the header file `pointer.hpp` to make these
+specializations available to the compiler when needed.
+The same file also contains many examples for the users that are interested in
+making their own containers available to the meta system.
+
+When a type is recognized as a pointer-like one by the meta system, it's
+possible to dereference the instances of `meta_any` that contain these objects.
+The following is a deliberately verbose example to show how to use this feature:
+
+```cpp
+int value = 42;
+// meta type equivalent to that of int *
+entt::meta_any any{&value};
+
+if(any.type().is_meta_pointer_like()) {
+ // meta type equivalent to that of int
+ if(entt::meta_any ref = *any; ref) {
+ // ...
+ }
+}
+```
+
+It goes without saying that it's not necessary to perform a double check.
+Instead, it's sufficient to query the meta type or verify that the returned
+object is valid. For example, invalid instances are returned when the wrapped
+object hasn't a pointer-like type.
+Note that dereferencing a pointer-like object returns an instance of `meta_any`
+which refers to the pointed object and allows users to modify it directly.
+
## Policies: the more, the less
Policies are a kind of compile-time directives that can be used when recording