Browse Source

view:
* removed view_pack and its tests
* added the possibility of combining different views
* updated documentation

Michele Caini 5 years ago
parent
commit
5a4b067cee
8 changed files with 65 additions and 906 deletions
  1. 0 1
      TODO
  2. 10 48
      docs/md/entity.md
  3. 24 0
      src/entt/entity/view.hpp
  4. 0 435
      src/entt/entity/view_pack.hpp
  5. 0 1
      src/entt/entt.hpp
  6. 0 1
      test/CMakeLists.txt
  7. 31 0
      test/entt/entity/view.cpp
  8. 0 420
      test/entt/entity/view_pack.cpp

+ 0 - 1
TODO

@@ -11,7 +11,6 @@ WIP:
 * HP: make const registry::view thread safe, switch to a view<T...>{registry} model (long term goal)
 * HP: review registry::get, registry::try_get
 * HP: weak reference wrapper example with custom storage
-* HP: merge view and view pack
 * HP: paginate pools
 * HP: headless (sparse set only) view
 * HP: write documentation for custom storages and views!!

+ 10 - 48
docs/md/entity.md

@@ -1344,14 +1344,12 @@ registry during iterations to get the types iterated by the view itself.
 
 ### View pack
 
-The view pack allows users to combine multiple views into a single _view-like_
-iterable object, while also giving them full control over which view should lead
-the iteration.<br/>
-This object returns all and only the entities present in all views. Its intended
-primary use is for custom storage and views, but it can also be very convenient
-in everyday use.
+Views can be combined with each other to create new and more specific
+objects.<br/>
+The type returned when combining multiple views together is itself a view, more
+in general a multi component one.
 
-The creation of a view pack tries to mimic C++20 ranges:
+Combining different views tries to mimic C++20 ranges:
 
 ```cpp
 auto view = registry.view<position>();
@@ -1360,47 +1358,11 @@ auto other = registry.view<velocity>();
 auto pack = view | other;
 ```
 
-The return type is a specialization of the class template `entt::view_pack`.
-This is nothing more than a _view-like_ iterable object that combines two or
-more views into a single instance.<br/>
-The first view used to create a pack will also be the same that will lead the
-iteration.
-
-A view pack offers functionalities similar to those of a multi component view,
-especially with regard to the possibilities of iteration. In particular, it only
-returns entities if iterated directly:
-
-```cpp
-for(auto entt: pack) {
-    // ...
-}
-```
-
-On the other hand, both the (optional) entity and the components are returned
-when the `each` member function is used, be it with callback or to get an
-extended iterable object:
-
-```cpp
-// with a callback
-pack.each([](const auto entt, auto &pos, auto &vel) { /* ... */ });
-
-// with an extended iterable object
-for(auto [entt, pos, vel]: pack.each()) {
-    // ...
-}
-```
-
-Furthermore, the constness of the types returned by a view pack is directly
-inherited by the views that compose it:
-
-```
-(registry.view<position>() | registry.view<const velocity>()).each([](auto &pos, const auto &vel) {
-    // ...
-});
-```
-
-Read also the dedicated section to know how a view pack is involved in the
-creation and use of custom storage and pools.
+The constness of the types is preserved and their order depends on the order in
+which the views are combined. Therefore, the pack in the example above will
+return an instance of `position` first and then one of `velocity`.<br/>
+Since combining views generates views, a chain can be of arbitrary length and
+the above type order rules apply sequentially.
 
 ### Runtime views
 

+ 24 - 0
src/entt/entity/view.hpp

@@ -533,6 +533,9 @@ public:
         return iterable_view{*this};
     }
 
+    template<typename Id, typename... ELhs, typename... CLhs, typename... ERhs, typename... CRhs>
+    friend auto operator|(const basic_view<Id, exclude_t<ELhs...>, CLhs...> &, const basic_view<Id, exclude_t<ERhs...>, CRhs...> &);
+
 private:
     const std::tuple<storage_type<Component> *...> pools;
     const std::tuple<const storage_type<Exclude> *...> filter;
@@ -920,6 +923,9 @@ public:
         return iterable_view{*std::get<0>(pools)};
     }
 
+    template<typename Id, typename... ELhs, typename... CLhs, typename... ERhs, typename... CRhs>
+    friend auto operator|(const basic_view<Id, exclude_t<ELhs...>, CLhs...> &, const basic_view<Id, exclude_t<ERhs...>, CRhs...> &);
+
 private:
     const std::tuple<storage_type *> pools;
     const std::tuple<> filter;
@@ -936,6 +942,24 @@ basic_view(Storage &... storage) ENTT_NOEXCEPT
 -> basic_view<std::common_type_t<typename Storage::entity_type...>, entt::exclude_t<>, constness_as_t<typename Storage::value_type, Storage>...>;
 
 
+/**
+ * @brief Combines two views in a _more specific_ one.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ * @tparam ELhs Filter list of the first view.
+ * @tparam CLhs Component list of the first view.
+ * @tparam ERhs Filter list of the second view.
+ * @tparam CRhs Component list of the second view.
+ * @param lhs A valid reference to the first view.
+ * @param rhs A valid reference to the second view.
+ * @return A more specific and correctly initialized view.
+ */
+template<typename Entity, typename... ELhs, typename... CLhs, typename... ERhs, typename... CRhs>
+[[nodiscard]] auto operator|(const basic_view<Entity, exclude_t<ELhs...>, CLhs...> &lhs, const basic_view<Entity, exclude_t<ERhs...>, CRhs...> &rhs) {
+    using view_type = basic_view<Entity, exclude_t<ELhs..., ERhs...>, CLhs..., CRhs...>;
+    return std::apply([](auto *... storage) { return view_type{*storage...}; }, std::tuple_cat(lhs.pools, rhs.pools, lhs.filter, rhs.filter));
+}
+
+
 }
 
 

+ 0 - 435
src/entt/entity/view_pack.hpp

@@ -1,435 +0,0 @@
-#ifndef ENTT_ENTITY_VIEW_PACK_HPP
-#define ENTT_ENTITY_VIEW_PACK_HPP
-
-
-#include <iterator>
-#include <tuple>
-#include <type_traits>
-#include <utility>
-#include "../core/type_traits.hpp"
-#include "fwd.hpp"
-#include "utility.hpp"
-
-
-namespace entt {
-
-
-/**
- * @brief View pack.
- *
- * The view pack allows users to combine multiple views into a single iterable
- * object, while also giving them full control over which view should lead the
- * iteration.<br/>
- * This class returns all and only the entities present in all views. Its
- * intended primary use is for custom storage and views, but it can also be very
- * convenient in everyday use.
- *
- * @tparam Head Type of the leading view of the pack.
- * @tparam Tail Types of all other views of the pack.
- */
-template<typename Head, typename... Tail>
-class view_pack {
-    template<typename It>
-    class view_pack_iterator final {
-        friend class view_pack<Head, Tail...>;
-
-        view_pack_iterator(It from, It to, const std::tuple<Tail...> &other) ENTT_NOEXCEPT
-            : it{from},
-              last{to},
-              tail{other}
-        {
-            if(it != last && !valid()) {
-                ++(*this);
-            }
-        }
-
-        [[nodiscard]] bool valid() const {
-            return std::apply([entity = *it](auto &&... curr) { return (curr.contains(entity) && ...); }, tail);
-        }
-
-    public:
-        using difference_type = typename std::iterator_traits<It>::difference_type;
-        using value_type = typename std::iterator_traits<It>::value_type;
-        using pointer = typename std::iterator_traits<It>::pointer;
-        using reference = typename std::iterator_traits<It>::reference;
-        using iterator_category = std::input_iterator_tag;
-
-        view_pack_iterator & operator++() ENTT_NOEXCEPT {
-            while(++it != last && !valid());
-            return *this;
-        }
-
-        view_pack_iterator operator++(int) ENTT_NOEXCEPT {
-            view_pack_iterator orig = *this;
-            return ++(*this), orig;
-        }
-
-        [[nodiscard]] reference operator*() const {
-            return *it;
-        }
-
-        [[nodiscard]] bool operator==(const view_pack_iterator &other) const ENTT_NOEXCEPT {
-            return other.it == it;
-        }
-
-        [[nodiscard]] bool operator!=(const view_pack_iterator &other) const ENTT_NOEXCEPT {
-            return !(*this == other);
-        }
-
-    private:
-        It it;
-        const It last;
-        const std::tuple<Tail...> tail;
-    };
-
-    class iterable_view_pack final {
-        friend class view_pack<Head, Tail...>;
-
-        using iterable_view = decltype(std::declval<Head>().each());
-
-        template<typename It>
-        class iterable_view_pack_iterator final {
-            friend class iterable_view_pack;
-
-            iterable_view_pack_iterator(It from, It to, const std::tuple<Tail...> &other) ENTT_NOEXCEPT
-                : it{from},
-                  last{to},
-                  tail{other}
-            {
-                if(it != last && !valid()) {
-                    ++(*this);
-                }
-            }
-
-            [[nodiscard]] bool valid() const {
-                return std::apply([entity = std::get<0>(*it)](auto &&... curr) { return (curr.contains(entity) && ...); }, tail);
-            }
-
-        public:
-            using difference_type = typename std::iterator_traits<It>::difference_type;
-            using value_type = decltype(std::tuple_cat(*std::declval<It>(), std::declval<Tail>().get({})...));
-            using pointer = void;
-            using reference = value_type;
-            using iterator_category = std::input_iterator_tag;
-
-            iterable_view_pack_iterator & operator++() ENTT_NOEXCEPT {
-                while(++it != last && !valid());
-                return *this;
-            }
-
-            iterable_view_pack_iterator operator++(int) ENTT_NOEXCEPT {
-                iterable_view_pack_iterator orig = *this;
-                return ++(*this), orig;
-            }
-
-            [[nodiscard]] reference operator*() const {
-                return std::apply([value = *it](auto &&... curr) { return std::tuple_cat(value, curr.get(std::get<0>(value))...); }, tail);
-            }
-
-            [[nodiscard]] bool operator==(const iterable_view_pack_iterator &other) const ENTT_NOEXCEPT {
-                return other.it == it;
-            }
-
-            [[nodiscard]] bool operator!=(const iterable_view_pack_iterator &other) const ENTT_NOEXCEPT {
-                return !(*this == other);
-            }
-
-        private:
-            It it;
-            const It last;
-            const std::tuple<Tail...> tail;
-        };
-
-        iterable_view_pack(const Head &first, const std::tuple<Tail...> &last)
-            : iterable{first.each()},
-              tail{last}
-        {}
-
-    public:
-        using iterator = iterable_view_pack_iterator<typename iterable_view::iterator>;
-        using reverse_iterator = iterable_view_pack_iterator<typename iterable_view::reverse_iterator>;
-
-        [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-            return { iterable.begin(), iterable.end(), tail };
-        }
-
-        [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
-            return { iterable.end(), iterable.end(), tail };
-        }
-
-        [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
-            return { iterable.rbegin(), iterable.rend(), tail };
-        }
-
-        [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
-            return { iterable.rend(), iterable.rend(), tail };
-        }
-
-    private:
-        iterable_view iterable;
-        std::tuple<Tail...> tail;
-    };
-
-public:
-    /*! @brief Underlying entity identifier. */
-    using entity_type = std::common_type_t<typename Head::entity_type, typename Tail::entity_type...>;
-    /*! @brief Underlying entity identifier. */
-    using size_type = std::common_type_t<typename Head::size_type, typename Tail::size_type...>;
-    /*! @brief Input iterator type. */
-    using iterator = view_pack_iterator<typename Head::iterator>;
-    /*! @brief Reversed iterator type. */
-    using reverse_iterator = view_pack_iterator<typename Head::reverse_iterator>;
-
-    /**
-     * @brief Constructs a pack from a bunch of views.
-     * @param first A reference to the leading view for the pack.
-     * @param last References to the other views to use to construct the pack.
-     */
-    view_pack(const Head &first, const Tail &... last)
-        : head{first},
-          tail{last...}
-    {}
-
-    /**
-     * @brief Returns an iterator to the first entity of the pack.
-     *
-     * The returned iterator points to the first entity of the pack. If the pack
-     * is empty, the returned iterator will be equal to `end()`.
-     *
-     * @return An iterator to the first entity of the pack.
-     */
-    [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-        return { head.begin(), head.end(), tail };
-    }
-
-    /**
-     * @brief Returns an iterator that is past the last entity of the pack.
-     *
-     * The returned iterator points to the entity following the last entity of
-     * the pack. Attempting to dereference the returned iterator results in
-     * undefined behavior.
-     *
-     * @return An iterator to the entity following the last entity of the pack.
-     */
-    [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
-        return { head.end(), head.end(), tail };
-    }
-
-    /**
-     * @brief Returns an iterator to the first entity of the pack.
-     *
-     * The returned iterator points to the first entity of the reversed pack. If
-     * the pack is empty, the returned iterator will be equal to `rend()`.
-     *
-     * @return An iterator to the first entity of the pack.
-     */
-    [[nodiscard]] reverse_iterator rbegin() const {
-        return { head.rbegin(), head.rend(), tail };
-    }
-
-    /**
-     * @brief Returns an iterator that is past the last entity of the reversed
-     * pack.
-     *
-     * The returned iterator points to the entity following the last entity of
-     * the reversed pack. Attempting to dereference the returned iterator
-     * results in undefined behavior.
-     *
-     * @return An iterator to the entity following the last entity of the
-     * reversed pack.
-     */
-    [[nodiscard]] reverse_iterator rend() const {
-        return { head.rend(), head.rend(), tail };
-    }
-
-    /**
-     * @brief Returns the first entity of the pack, if any.
-     * @return The first entity of the pack if one exists, the null entity
-     * otherwise.
-     */
-    [[nodiscard]] entity_type front() const {
-        const auto it = begin();
-        return it != end() ? *it : null;
-    }
-
-    /**
-     * @brief Returns the last entity of the pack, if any.
-     * @return The last entity of the pack if one exists, the null entity
-     * otherwise.
-     */
-    [[nodiscard]] entity_type back() const {
-        const auto it = rbegin();
-        return it != rend() ? *it : null;
-    }
-
-    /**
-     * @brief Finds an entity.
-     * @param entt A valid entity identifier.
-     * @return An iterator to the given entity if it's found, past the end
-     * iterator otherwise.
-     */
-    [[nodiscard]] iterator find(const entity_type entt) const {
-        iterator it{head.find(entt), head.end(), tail};
-        return (it != end() && *it == entt) ? it : end();
-    }
-
-    /**
-     * @brief Checks if a pack contains an entity.
-     * @param entt A valid entity identifier.
-     * @return True if the pack contains the given entity, false otherwise.
-     */
-    [[nodiscard]] bool contains(const entity_type entt) const {
-        return head.contains(entt) && std::apply([entt](auto &&... curr) { return (curr.contains(entt) && ...); }, tail);
-    }
-
-    /**
-     * @brief Returns the components assigned to the given entity.
-     *
-     * Prefer this function instead of `registry::get` during iterations. It has
-     * far better performance than its counterpart.
-     *
-     * @warning
-     * Attempting to use an invalid component type results in a compilation
-     * error. Attempting to use an entity that doesn't belong to the pack
-     * results in undefined behavior.
-     *
-     * @tparam Comp Types of components to get.
-     * @param entt A valid entity identifier.
-     * @return The components assigned to the entity.
-     */
-    template<typename... Comp>
-    [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) const {
-        ENTT_ASSERT(contains(entt));
-        auto component = std::apply([this, entt](auto &&... curr) { return std::tuple_cat(head.get(entt), curr.get(entt)...); }, tail);
-
-        if constexpr(sizeof...(Comp) == 0) {
-            return component;
-        } else if constexpr(sizeof...(Comp) == 1) {
-            return (std::get<Comp &>(component), ...);
-        } else {
-            return std::forward_as_tuple(std::get<Comp &>(component)...);
-        }
-    }
-
-    /**
-     * @brief Iterates entities and components and applies the given function
-     * object to them.
-     *
-     * The function object is invoked for each entity. It is provided with the
-     * entity itself and a set of references to non-empty components. The
-     * _constness_ of the components is as requested.<br/>
-     * The signature of the function must be equivalent to one of the following
-     * forms:
-     *
-     * @code{.cpp}
-     * void(const entity_type, Type &...);
-     * void(Type &...);
-     * @endcode
-     *
-     * @note
-     * Empty types aren't explicitly instantiated and therefore they are never
-     * returned during iterations.
-     *
-     * @tparam Func Type of the function object to invoke.
-     * @param func A valid function object.
-     */
-    template<typename Func>
-    void each(Func func) const {
-        for(auto &&value: head.each()) {
-            if(std::apply([&value](auto &&... curr) { return (curr.contains(std::get<0>(value)) && ...); }, tail)) {
-                auto args = std::apply([&value](auto &&... curr) { return std::tuple_cat(value, curr.get(std::get<0>(value))...); }, tail);
-
-                if constexpr(is_applicable_v<Func, decltype(args)>) {
-                    std::apply(func, args);
-                } else {
-                    std::apply([&func](const auto, auto &&... component) { func(std::forward<decltype(component)>(component)...); }, args);
-                }
-            }
-        }
-    }
-
-    /**
-     * @brief Returns an iterable object to use to _visit_ the pack.
-     *
-     * The iterable object returns tuples that contain the current entity and a
-     * set of references to its non-empty components. The _constness_ of the
-     * components is as requested.
-     *
-     * @note
-     * Empty types aren't explicitly instantiated and therefore they are never
-     * returned during iterations.
-     *
-     * @return An iterable object to use to _visit_ the pack.
-     */
-    [[nodiscard]] iterable_view_pack each() const ENTT_NOEXCEPT {
-        return { head, tail };
-    }
-
-    /**
-     * @brief Returns a copy of the views stored by the pack.
-     * @return A copy of the views stored by the pack.
-     */
-    std::tuple<Head, Tail...> pack() const ENTT_NOEXCEPT {
-        return std::apply([this](auto &&... curr) { return std::make_tuple(head, curr...); }, tail);
-    }
-
-    /**
-     * @brief Appends a view to a pack.
-     * @tparam Args View template arguments.
-     * @param other A reference to a view to append to the pack.
-     * @return The extended pack.
-     */
-    template<typename... Args>
-    [[nodiscard]] auto operator|(const basic_view<Args...> &other) const {
-        return std::make_from_tuple<view_pack<Head, Tail..., basic_view<Args...>>>(std::tuple_cat(std::make_tuple(head), tail, std::make_tuple(other)));
-    }
-
-    /**
-     * @brief Appends a pack and therefore all its views to another pack.
-     * @tparam Pack Types of views of the pack to append.
-     * @param other A reference to the pack to append.
-     * @return The extended pack.
-     */
-    template<typename... Pack>
-    [[nodiscard]] auto operator|(const view_pack<Pack...> &other) const {
-        return std::make_from_tuple<view_pack<Head, Tail..., Pack...>>(std::tuple_cat(std::make_tuple(head), tail, other.pack()));
-    }
-
-private:
-    Head head;
-    std::tuple<Tail...> tail;
-};
-
-
-/**
- * @brief Combines two views in a pack.
- * @tparam Args Template arguments of the first view.
- * @tparam Other Template arguments of the second view.
- * @param lhs A reference to the first view with which to create the pack.
- * @param rhs A reference to the second view with which to create the pack.
- * @return A pack that combines the two views in a single iterable object.
- */
-template<typename... Args, typename... Other>
-[[nodiscard]] auto operator|(const basic_view<Args...> &lhs, const basic_view<Other...> &rhs) {
-    return view_pack{lhs, rhs};
-}
-
-
-/**
- * @brief Combines a view with a pack.
- * @tparam Args View template arguments.
- * @tparam Pack Types of views of the pack.
- * @param view A reference to the view to combine with the pack.
- * @param pack A reference to the pack to combine with the view.
- * @return The extended pack.
- */
-template<typename... Args, typename... Pack>
-[[nodiscard]] auto operator|(const basic_view<Args...> &view, const view_pack<Pack...> &pack) {
-    return view_pack{view} | pack;
-}
-
-
-}
-
-
-#endif

+ 0 - 1
src/entt/entt.hpp

@@ -23,7 +23,6 @@
 #include "entity/storage.hpp"
 #include "entity/utility.hpp"
 #include "entity/view.hpp"
-#include "entity/view_pack.hpp"
 #include "locator/locator.hpp"
 #include "meta/adl_pointer.hpp"
 #include "meta/container.hpp"

+ 0 - 1
test/CMakeLists.txt

@@ -179,7 +179,6 @@ SETUP_BASIC_TEST(snapshot entt/entity/snapshot.cpp)
 SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp)
 SETUP_BASIC_TEST(storage entt/entity/storage.cpp)
 SETUP_BASIC_TEST(view entt/entity/view.cpp)
-SETUP_BASIC_TEST(view_pack entt/entity/view_pack.cpp)
 
 # Test locator
 

+ 31 - 0
test/entt/entity/view.cpp

@@ -904,3 +904,34 @@ TEST(MultiComponentView, DeductionGuide) {
     static_assert(std::is_same_v<decltype(entt::basic_view{istorage, std::as_const(dstorage)}), entt::basic_view<entt::entity, entt::exclude_t<>, int, const double>>);
     static_assert(std::is_same_v<decltype(entt::basic_view{std::as_const(istorage), std::as_const(dstorage)}), entt::basic_view<entt::entity, entt::exclude_t<>, const int, const double>>);
 }
+
+TEST(View, Pipe) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    const auto other = registry.create();
+
+    registry.emplace<int>(entity);
+    registry.emplace<char>(entity);
+    registry.emplace<double>(entity);
+    registry.emplace<empty_type>(entity);
+
+    registry.emplace<int>(other);
+    registry.emplace<char>(other);
+
+    const auto view1 = registry.view<int>(entt::exclude<double>);
+    const auto view2 = registry.view<const char>(entt::exclude<float>);
+    const auto view3 = registry.view<empty_type>();
+
+    static_assert(std::is_same_v<decltype(view1 | view2), entt::basic_view<entt::entity, entt::exclude_t<double, float>, int, const char>>);
+    static_assert(std::is_same_v<decltype(view2 | view1), entt::basic_view<entt::entity, entt::exclude_t<float, double>, const char, int>>);
+    static_assert(std::is_same_v<decltype((view1 | view2) | view3), decltype(view1 | (view2 | view3))>);
+
+    ASSERT_FALSE((view1 | view2).contains(entity));
+    ASSERT_TRUE((view1 | view2).contains(other));
+
+    ASSERT_TRUE((view2 | view3).contains(entity));
+    ASSERT_FALSE((view2 | view3).contains(other));
+
+    ASSERT_FALSE((view1 | view2 | view3).contains(entity));
+    ASSERT_FALSE((view1 | view2 | view3).contains(other));
+}

+ 0 - 420
test/entt/entity/view_pack.cpp

@@ -1,420 +0,0 @@
-#include <cstdint>
-#include <iterator>
-#include <type_traits>
-#include <gtest/gtest.h>
-#include <entt/entity/registry.hpp>
-#include <entt/entity/view.hpp>
-#include <entt/entity/view_pack.hpp>
-
-struct empty_type {};
-
-TEST(ViewPack, Construction) {
-    entt::registry registry;
-
-    const auto view1 = registry.view<int, const char>();
-    const auto view2 = registry.view<empty_type>();
-    const auto view3 = registry.view<double>();
-
-    static_assert(std::is_same_v<decltype(entt::view_pack{view1}), entt::view_pack<entt::view<entt::exclude_t<>, int, const char>>>);
-    static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(view1 | view2 | view3)>);
-
-    static_assert(std::is_same_v<
-        decltype(entt::view_pack{view1, view2, view3}),
-        entt::view_pack<
-            entt::view<entt::exclude_t<>, int, const char>,
-            entt::view<entt::exclude_t<>, empty_type>,
-            entt::view<entt::exclude_t<>, double>
-        >
-    >);
-
-    static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(entt::view_pack{view1} | view2 | view3)>);
-    static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(view1 | entt::view_pack{view2} | view3)>);
-    static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(view1 | view2 | entt::view_pack{view3})>);
-}
-
-TEST(ViewPack, Functionalities) {
-    entt::registry registry;
-    const auto pack = registry.view<int>() | registry.view<char>();
-    const auto cpack = registry.view<const int>() | registry.view<const char>();
-
-    const auto e0 = registry.create();
-    registry.emplace<char>(e0, '1');
-
-    const auto e1 = registry.create();
-    registry.emplace<int>(e1, 42);
-    registry.emplace<char>(e1, '2');
-
-    ASSERT_EQ(*pack.begin(), e1);
-    ASSERT_EQ(*pack.rbegin(), e1);
-    ASSERT_EQ(++pack.begin(), (pack.end()));
-    ASSERT_EQ(++pack.rbegin(), (pack.rend()));
-
-    ASSERT_NO_THROW((pack.begin()++));
-    ASSERT_NO_THROW((++cpack.begin()));
-    ASSERT_NO_THROW(pack.rbegin()++);
-    ASSERT_NO_THROW(++cpack.rbegin());
-
-    ASSERT_NE(pack.begin(), pack.end());
-    ASSERT_NE(cpack.begin(), cpack.end());
-    ASSERT_NE(pack.rbegin(), pack.rend());
-    ASSERT_NE(cpack.rbegin(), cpack.rend());
-
-    for(auto entity: pack) {
-        ASSERT_EQ(std::get<0>(cpack.get<const int, const char>(entity)), 42);
-        ASSERT_EQ(std::get<1>(pack.get<int, char>(entity)), '2');
-        ASSERT_EQ(cpack.get<const char>(entity), '2');
-    }
-}
-
-TEST(ViewPack, Iterator) {
-    entt::registry registry;
-    const auto entity = registry.create();
-    registry.emplace<int>(entity);
-    registry.emplace<char>(entity);
-
-    const auto pack = registry.view<int>() | registry.view<char>();
-
-    ASSERT_NE(pack.begin(), pack.end());
-    ASSERT_EQ(pack.begin()++, pack.begin());
-    ASSERT_EQ(++pack.begin(), pack.end());
-    ASSERT_EQ(*pack.begin(), entity);
-}
-
-TEST(ViewPack, ReverseIterator) {
-    entt::registry registry;
-    const auto entity = registry.create();
-    registry.emplace<int>(entity);
-    registry.emplace<char>(entity);
-
-    const auto pack = registry.view<int>() | registry.view<char>();
-
-    ASSERT_NE(pack.rbegin(), pack.rend());
-    ASSERT_EQ(pack.rbegin()++, pack.rbegin());
-    ASSERT_EQ(++pack.rbegin(), pack.rend());
-    ASSERT_EQ(*pack.rbegin(), entity);
-}
-
-TEST(ViewPack, Contains) {
-    entt::registry registry;
-
-    const auto e0 = registry.create();
-    registry.emplace<int>(e0);
-    registry.emplace<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.emplace<int>(e1);
-    registry.emplace<char>(e1);
-
-    registry.destroy(e0);
-
-    const auto pack = registry.view<int>() | registry.view<char>();
-
-    ASSERT_FALSE(pack.contains(e0));
-    ASSERT_TRUE(pack.contains(e1));
-}
-
-TEST(ViewPack, Each) {
-    entt::registry registry;
-
-    const auto e0 = registry.create();
-    registry.emplace<int>(e0, 0);
-    registry.emplace<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.emplace<int>(e1, 1);
-    registry.emplace<char>(e1);
-
-    auto pack = registry.view<int>() | registry.view<char>();
-    auto cpack = registry.view<const int>() | registry.view<const char>();
-    std::size_t cnt = 0;
-
-    for(auto first = cpack.each().rbegin(), last = cpack.each().rend(); first != last; ++first) {
-        static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &, const char &>>);
-        ASSERT_EQ(std::get<1>(*first), cnt++);
-    }
-
-    pack.each([&cnt](auto, int &, char &) { ++cnt; });
-    pack.each([&cnt](int &, char &) { ++cnt; });
-
-    ASSERT_EQ(cnt, std::size_t{6});
-
-    cpack.each([&cnt](const int &, const char &) { --cnt; });
-    cpack.each([&cnt](auto, const int &, const char &) { --cnt; });
-
-    for(auto [entt, iv, cv]: pack.each()) {
-        static_assert(std::is_same_v<decltype(entt), entt::entity>);
-        static_assert(std::is_same_v<decltype(iv), int &>);
-        static_assert(std::is_same_v<decltype(cv), char &>);
-        ASSERT_EQ(iv, --cnt);
-    }
-
-    ASSERT_EQ(cnt, std::size_t{0});
-}
-
-TEST(ViewPack, EachWithHoles) {
-    entt::registry registry;
-
-    const auto e0 = registry.create();
-    const auto e1 = registry.create();
-    const auto e2 = registry.create();
-
-    registry.emplace<char>(e0, '0');
-    registry.emplace<char>(e1, '1');
-
-    registry.emplace<int>(e0, 0);
-    registry.emplace<int>(e2, 2);
-
-    auto pack = registry.view<char>() | registry.view<int>();
-
-    pack.each([e0](auto entity, const char &c, const int &i) {
-        ASSERT_EQ(entity, e0);
-        ASSERT_EQ(c, '0');
-        ASSERT_EQ(i, 0);
-    });
-
-    for(auto &&curr: pack.each()) {
-        ASSERT_EQ(std::get<0>(curr), e0);
-        ASSERT_EQ(std::get<1>(curr), '0');
-        ASSERT_EQ(std::get<2>(curr), 0);
-    }
-}
-
-TEST(ViewPack, ConstNonConstAndAllInBetween) {
-    entt::registry registry;
-    auto pack = registry.view<int, empty_type>() | registry.view<const char>();
-
-    const auto entity = registry.create();
-    registry.emplace<int>(entity, 0);
-    registry.emplace<empty_type>(entity);
-    registry.emplace<char>(entity, 'c');
-
-    static_assert(std::is_same_v<decltype(pack.get<int>({})), int &>);
-    static_assert(std::is_same_v<decltype(pack.get<const char>({})), const char &>);
-    static_assert(std::is_same_v<decltype(pack.get<int, const char>({})), std::tuple<int &, const char &>>);
-    static_assert(std::is_same_v<decltype(pack.get({})), std::tuple<int &, const char &>>);
-
-    pack.each([](auto &&i, auto &&c) {
-        static_assert(std::is_same_v<decltype(i), int &>);
-        static_assert(std::is_same_v<decltype(c), const char &>);
-    });
-
-    for(auto [entt, iv, cv]: pack.each()) {
-        static_assert(std::is_same_v<decltype(entt), entt::entity>);
-        static_assert(std::is_same_v<decltype(iv), int &>);
-        static_assert(std::is_same_v<decltype(cv), const char &>);
-    }
-}
-
-TEST(ViewPack, Find) {
-    entt::registry registry;
-    auto pack = registry.view<int>() | registry.view<const char>();
-
-    const auto e0 = registry.create();
-    registry.emplace<int>(e0);
-    registry.emplace<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.emplace<int>(e1);
-    registry.emplace<char>(e1);
-
-    const auto e2 = registry.create();
-    registry.emplace<int>(e2);
-    registry.emplace<char>(e2);
-
-    const auto e3 = registry.create();
-    registry.emplace<int>(e3);
-    registry.emplace<char>(e3);
-
-    registry.remove<int>(e1);
-
-    ASSERT_NE(pack.find(e0), pack.end());
-    ASSERT_EQ(pack.find(e1), pack.end());
-    ASSERT_NE(pack.find(e2), pack.end());
-    ASSERT_NE(pack.find(e3), pack.end());
-
-    auto it = pack.find(e2);
-
-    ASSERT_EQ(*it, e2);
-    ASSERT_EQ(*(++it), e3);
-    ASSERT_EQ(*(++it), e0);
-    ASSERT_EQ(++it, pack.end());
-    ASSERT_EQ(++pack.find(e0), pack.end());
-
-    const auto e4 = registry.create();
-    registry.destroy(e4);
-    const auto e5 = registry.create();
-    registry.emplace<int>(e5);
-    registry.emplace<char>(e5);
-
-    ASSERT_NE(pack.find(e5), pack.end());
-    ASSERT_EQ(pack.find(e4), pack.end());
-}
-
-TEST(ViewPack, FrontBack) {
-    entt::registry registry;
-    auto pack = registry.view<const int>() | registry.view<const char>();
-
-    ASSERT_EQ(pack.front(), static_cast<entt::entity>(entt::null));
-    ASSERT_EQ(pack.back(), static_cast<entt::entity>(entt::null));
-
-    const auto e0 = registry.create();
-    registry.emplace<int>(e0);
-    registry.emplace<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.emplace<int>(e1);
-    registry.emplace<char>(e1);
-
-    const auto entity = registry.create();
-    registry.emplace<char>(entity);
-
-    ASSERT_EQ(pack.front(), e1);
-    ASSERT_EQ(pack.back(), e0);
-}
-
-TEST(ViewPack, ShortestPool) {
-    entt::registry registry;
-    entt::entity entities[4];
-
-    registry.create(std::begin(entities), std::end(entities));
-
-    registry.insert<int>(std::begin(entities), std::end(entities));
-    registry.insert<empty_type>(std::begin(entities), std::end(entities));
-    registry.insert<char>(std::rbegin(entities) + 1u, std::rend(entities) - 1u);
-
-    const auto tmp = registry.view<char>() | registry.view<empty_type>();
-    const auto pack = tmp | registry.view<const int>();
-
-    {
-        std::size_t next{};
-
-        for(const auto entt: pack) {
-            ASSERT_EQ(entt::to_integral(entt), ++next);
-            ASSERT_TRUE((registry.all_of<int, char>(entt)));
-        }
-    }
-
-    auto it = pack.begin();
-
-    ASSERT_EQ(*it++, entities[1u]);
-    ASSERT_EQ(++it, pack.end());
-    ASSERT_TRUE(it == pack.end());
-    ASSERT_FALSE(it != pack.end());
-
-    pack.each([&registry, next = 0u](const auto entt, auto &&cv, auto &&iv) mutable {
-        static_assert(std::is_same_v<decltype(entt), const entt::entity>);
-        static_assert(std::is_same_v<decltype(cv), char &>);
-        static_assert(std::is_same_v<decltype(iv), const int &>);
-        ASSERT_EQ(entt::to_integral(entt), ++next);
-        ASSERT_EQ(&cv, registry.try_get<char>(entt));
-        ASSERT_EQ(&iv, registry.try_get<int>(entt));
-    });
-
-    pack.each([](auto &&cv, auto &&iv) {
-        static_assert(std::is_same_v<decltype(cv), char &>);
-        static_assert(std::is_same_v<decltype(iv), const int &>);
-    });
-
-    auto eit = pack.each().begin();
-
-    ASSERT_EQ(std::get<0>(*eit++), entities[1u]);
-    static_assert(std::is_same_v<decltype(std::get<1>(*eit)), char &>);
-    static_assert(std::is_same_v<decltype(std::get<2>(*eit)), const int &>);
-    ASSERT_EQ(++eit, pack.each().end());
-    ASSERT_TRUE(eit == pack.each().end());
-    ASSERT_FALSE(eit != pack.each().end());
-
-    {
-        std::size_t next{};
-
-        for(const auto [entt, cv, iv]: pack.each()) {
-            static_assert(std::is_same_v<decltype(entt), const entt::entity>);
-            static_assert(std::is_same_v<decltype(cv), char &>);
-            static_assert(std::is_same_v<decltype(iv), const int &>);
-            ASSERT_EQ(entt::to_integral(entt), ++next);
-            ASSERT_EQ(&cv, registry.try_get<char>(entt));
-            ASSERT_EQ(&iv, registry.try_get<int>(entt));
-        }
-    }
-}
-
-TEST(ViewPack, LongestPool) {
-    entt::registry registry;
-    entt::entity entities[4];
-
-    registry.create(std::begin(entities), std::end(entities));
-
-    registry.insert<int>(std::begin(entities), std::end(entities));
-    registry.insert<empty_type>(std::begin(entities), std::end(entities));
-    registry.insert<char>(std::rbegin(entities) + 1u, std::rend(entities) - 1u);
-
-    const auto pack = registry.view<int>() | registry.view<empty_type>() | registry.view<const char>();
-
-    {
-        std::size_t next{2u};
-
-        for(const auto entt: pack) {
-            ASSERT_EQ(entt::to_integral(entt), next--);
-            ASSERT_TRUE((registry.all_of<int, char>(entt)));
-        }
-    }
-
-    auto it = pack.begin();
-
-    ASSERT_EQ(*it++, entities[2u]);
-    ASSERT_EQ(++it, pack.end());
-    ASSERT_TRUE(it == pack.end());
-    ASSERT_FALSE(it != pack.end());
-
-    pack.each([&registry, next = 2u](const auto entt, auto &&iv, auto &&cv) mutable {
-        static_assert(std::is_same_v<decltype(entt), const entt::entity>);
-        static_assert(std::is_same_v<decltype(iv), int &>);
-        static_assert(std::is_same_v<decltype(cv), const char &>);
-        ASSERT_EQ(entt::to_integral(entt), next--);
-        ASSERT_EQ(&iv, registry.try_get<int>(entt));
-        ASSERT_EQ(&cv, registry.try_get<char>(entt));
-    });
-
-    pack.each([](auto &&iv, auto &&cv) {
-        static_assert(std::is_same_v<decltype(iv), int &>);
-        static_assert(std::is_same_v<decltype(cv), const char &>);
-    });
-
-    auto eit = pack.each().begin();
-
-    ASSERT_EQ(std::get<0>(*eit++), entities[2u]);
-    static_assert(std::is_same_v<decltype(std::get<1>(*eit)), int &>);
-    static_assert(std::is_same_v<decltype(std::get<2>(*eit)), const char &>);
-    ASSERT_EQ(++eit, pack.each().end());
-    ASSERT_TRUE(eit == pack.each().end());
-    ASSERT_FALSE(eit != pack.each().end());
-
-    {
-        std::size_t next{2u};
-
-        for(const auto [entt, iv, cv]: pack.each()) {
-            static_assert(std::is_same_v<decltype(entt), const entt::entity>);
-            static_assert(std::is_same_v<decltype(iv), int &>);
-            static_assert(std::is_same_v<decltype(cv), const char &>);
-            ASSERT_EQ(entt::to_integral(entt), next--);
-            ASSERT_EQ(&iv, registry.try_get<int>(entt));
-            ASSERT_EQ(&cv, registry.try_get<char>(entt));
-        }
-    }
-}
-
-TEST(ViewPack, RepeatedType) {
-    entt::registry registry;
-    const auto entity = registry.create();
-
-    registry.emplace<int>(entity, 3);
-
-    const auto view = registry.view<int>();
-    const auto pack = view | view;
-
-    for(auto [entt, i1, i2]: pack.each()) {
-        ASSERT_EQ(entt, entity);
-        ASSERT_EQ(i1, 3);
-        ASSERT_EQ(i1, i2);
-    }
-}