Sfoglia il codice sorgente

views: const, non-const and all in between (fix #152)

Michele Caini 7 anni fa
parent
commit
c13fe3feb6

+ 57 - 2
docs/entity.md

@@ -25,13 +25,14 @@
     * [Dependency function](#dependency-function)
     * [Labels](#labels)
   * [Null entity](#null-entity)
-* [View: to persist or not to persist?](#view-to-persist-or-not-to-persist)
+* [Views: pay for what you use](#views-pay-for-what-you-use)
   * [Standard View](#standard-view)
     * [Single component standard view](#single-component-standard-view)
     * [Multi component standard view](#multi-component-standard-view)
   * [Persistent View](#persistent-view)
   * [Raw View](#raw-view)
   * [Runtime View](#runtime-view)
+  * [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between)
   * [Give me everything](#give-me-everything)
 * [Iterations: what is allowed and what is not](#iterations-what-is-allowed-and-what-is-not)
 * [Multithreading](#multithreading)
@@ -798,7 +799,7 @@ const auto entity = registry.create();
 const bool null = (entity == entt::null);
 ```
 
-# View: to persist or not to persist?
+# Views: pay for what you use
 
 First of all, it is worth answering an obvious question: why views?<br/>
 Roughly speaking, they are a good tool to enforce single responsibility. A
@@ -1185,6 +1186,60 @@ well suited to plugin systems and mods in general. Where possible, don't use
 runtime views, as their performance are slightly inferior to those of the other
 views.
 
+# Types: const, non-const and all in between
+
+The `registry` class offers two overloads for most of the member functions used
+to construct views: a const one and a non-const one. The former accepts both
+const and non-const types as template parameters, the latter accepts only const
+types instead.<br/>
+It means that views can be constructed also from a const registry and they
+require to propagate the constness of the registry to the types used to
+construct the views themselves:
+
+```cpp
+entt::view<const position, const velocity> view = std::as_const(registry).view<const position, const velocity>();
+```
+
+Consider the following definition for a non-const view instead:
+
+```cpp
+entt::view<position, const velocity> view = registry.view<position, const velocity>();
+```
+
+In the example above, `view` can be used to access either read-only or writable
+`position` components while `velocity` components are read-only in all
+cases.<br/>
+In other terms, these statements are all valid:
+
+```cpp
+position &pos = view.get<position>(entity);
+const position &cpos = view.get<const position>(entity);
+const velocity &cpos = view.get<const velocity>(entity);
+std::tuple<position &, const velocity &> tup = view.get<position, const velocity>(entity);
+std::tuple<const position &, const velocity &> ctup = view.get<const position, const velocity>(entity);
+```
+
+It's not possible to get non-const references to `velocity` components from the
+same view instead and these will result in compilation errors:
+
+```cpp
+velocity &cpos = view.get<velocity>(entity);
+std::tuple<position &, velocity &> tup = view.get<position, velocity>(entity);
+std::tuple<const position &, velocity &> ctup = view.get<const position, velocity>(entity);
+```
+
+Similarly, the `each` member functions will propagate constness to the type of
+the components returned during iterations:
+
+```cpp
+view.each([](const auto entity, position &pos, const velocity &vel) {
+    // ...
+});
+```
+
+Obviously, a caller can still refer to the `position` components through a const
+reference because of the rules of the language that fortunately already allow
+it.
 
 ## Give me everything
 

+ 2 - 2
src/entt/entity/prototype.hpp

@@ -186,7 +186,7 @@ public:
         if constexpr(sizeof...(Component) == 1) {
             return (std::as_const(*reg).template get<component_wrapper<Component...>>(entity).component);
         } else {
-            return std::tuple<const Component &...>{get<Component>()...};
+            return std::tuple<std::add_const_t<Component> &...>{get<Component>()...};
         }
     }
 
@@ -222,7 +222,7 @@ public:
             const auto *wrapper = reg->template try_get<component_wrapper<Component...>>(entity);
             return wrapper ? &wrapper->component : nullptr;
         } else {
-            return std::tuple<const Component *...>{try_get<Component>()...};
+            return std::tuple<std::add_const_t<Component> *...>{try_get<Component>()...};
         }
     }
 

+ 145 - 33
src/entt/entity/registry.hpp

@@ -110,42 +110,37 @@ class registry {
     }
 
     template<typename Component>
-    inline const component_pool<Component> & pool() const ENTT_NOEXCEPT {
+    inline auto & pool() const ENTT_NOEXCEPT {
         assert(managed<Component>());
-        return static_cast<const component_pool<Component> &>(*pools[component_family::type<Component>]);
-    }
-
-    template<typename Component>
-    inline component_pool<Component> & pool() ENTT_NOEXCEPT {
-        return const_cast<component_pool<Component> &>(std::as_const(*this).template pool<Component>());
+        return static_cast<component_pool<std::decay_t<Component>> &>(*pools[component_family::type<Component>]);
     }
 
     template<typename Comp, std::size_t Index, typename... Component, std::size_t... Indexes>
-    void connect(std::index_sequence<Indexes...>) {
+    void connect(std::index_sequence<Indexes...>) const {
         pool<Comp>().construction().template connect<&registry::creating<&registry::has<std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>, Component...>>();
         pool<Comp>().destruction().template connect<&registry::destroying<Comp, Index, Component...>>();
     }
 
     template<typename... Component, std::size_t... Indexes>
-    void connect(std::index_sequence<Indexes...>) {
+    void connect(std::index_sequence<Indexes...>) const {
         (assure<Component>(), ...);
         (connect<Component, Indexes, Component...>(std::make_index_sequence<sizeof...(Component)-1>{}), ...);
     }
 
     template<typename Comp, std::size_t Index, typename... Component, std::size_t... Indexes>
-    void disconnect(std::index_sequence<Indexes...>) {
+    void disconnect(std::index_sequence<Indexes...>) const {
         pool<Comp>().construction().template disconnect<&registry::creating<&registry::has<std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>, Component...>>();
         pool<Comp>().destruction().template disconnect<&registry::destroying<Comp, Index, Component...>>();
     }
 
     template<typename... Component, std::size_t... Indexes>
-    void disconnect(std::index_sequence<Indexes...>) {
+    void disconnect(std::index_sequence<Indexes...>) const {
         // if a set exists, pools have already been created for it
         (disconnect<Component, Indexes, Component...>(std::make_index_sequence<sizeof...(Component)-1>{}), ...);
     }
 
     template<typename Component>
-    void assure() {
+    void assure() const {
         const auto ctype = component_family::type<Component>;
 
         if(!(ctype < pools.size())) {
@@ -153,7 +148,7 @@ class registry {
         }
 
         if(!pools[ctype]) {
-            pools[ctype] = std::make_unique<component_pool<Component>>(this);
+            pools[ctype] = std::make_unique<component_pool<std::decay_t<Component>>>(const_cast<registry *>(this));
         }
     }
 
@@ -305,7 +300,7 @@ public:
      * @return A pointer to the array of components of the given type.
      */
     template<typename Component>
-    const Component * raw() const ENTT_NOEXCEPT {
+    std::add_const_t<Component> * raw() const ENTT_NOEXCEPT {
         return managed<Component>() ? pool<Component>().raw() : nullptr;
     }
 
@@ -478,6 +473,7 @@ public:
      */
     template<typename It>
     void create(It first, It last) {
+        static_assert(std::is_convertible_v<entity_type, typename std::iterator_traits<It>::value_type>);
         const auto length = size_type(last - first);
         const auto sz = std::min(available, length);
 
@@ -646,9 +642,9 @@ public:
         assert((managed<Component>() && ...));
 
         if constexpr(sizeof...(Component) == 1) {
-            return pool<Component...>().get(entity);
+            return std::as_const(pool<Component...>()).get(entity);
         } else {
-            return std::tuple<const Component &...>{get<Component>(entity)...};
+            return std::tuple<std::add_const_t<Component> &...>{get<Component>(entity)...};
         }
     }
 
@@ -724,9 +720,9 @@ public:
         assert(valid(entity));
 
         if constexpr(sizeof...(Component) == 1) {
-            return managed<Component...>() ? pool<Component...>().try_get(entity) : nullptr;
+            return managed<Component...>() ? std::as_const(pool<Component...>()).try_get(entity) : nullptr;
         } else {
-            return std::tuple<const Component *...>{try_get<Component>(entity)...};
+            return std::tuple<std::add_const_t<Component> *...>{try_get<Component>(entity)...};
         }
     }
 
@@ -773,7 +769,7 @@ public:
      */
     template<typename Component, typename... Args>
     Component & replace(const entity_type entity, Args &&... args) {
-        return (get<Component>(entity) = Component{std::forward<Args>(args)...});
+        return (pool<Component>().get(entity) = std::decay_t<Component>{std::forward<Args>(args)...});
     }
 
     /**
@@ -808,7 +804,7 @@ public:
         auto &cpool = pool<Component>();
 
         return cpool.has(entity)
-                ? cpool.get(entity) = Component{std::forward<Args>(args)...}
+                ? cpool.get(entity) = std::decay_t<Component>{std::forward<Args>(args)...}
                 : cpool.construct(entity, std::forward<Args>(args)...);
     }
 
@@ -1042,6 +1038,8 @@ public:
      */
     template<typename Func>
     void each(Func func) const {
+        static_assert(std::is_invocable_v<Func, entity_type>);
+
         if(available) {
             for(auto pos = entities.size(); pos; --pos) {
                 const auto curr = entity_type(pos - 1);
@@ -1097,6 +1095,8 @@ public:
      */
     template<typename Func>
     void orphans(Func func) const {
+        static_assert(std::is_invocable_v<Func, entity_type>);
+
         each([func = std::move(func), this](const auto entity) {
             if(orphan(entity)) {
                 func(entity);
@@ -1144,6 +1144,46 @@ public:
         return { &pool<Component>()... };
     }
 
+    /**
+     * @brief Returns a standard view for the given components.
+     *
+     * This kind of views are created on the fly and share with the registry its
+     * internal data structures.<br/>
+     * Feel free to discard a view after the use. Creating and destroying a view
+     * is an incredibly cheap operation because they do not require any type of
+     * initialization.<br/>
+     * As a rule of thumb, storing a view should never be an option.
+     *
+     * Standard views do their best to iterate the smallest set of candidate
+     * entities. In particular:
+     *
+     * * Single component views are incredibly fast and iterate a packed array
+     *   of entities, all of which has the given component.
+     * * Multi component views look at the number of entities available for each
+     *   component and pick up a reference to the smallest set of candidates to
+     *   test for the given components.
+     *
+     * @note
+     * Multi component views are pretty fast. However their performance tend to
+     * degenerate when the number of components to iterate grows up and the most
+     * of the entities have all the given components.<br/>
+     * To get a performance boost, consider using a persistent_view instead.
+     *
+     * @sa view
+     * @sa view<Entity, Component>
+     * @sa persistent_view
+     * @sa raw_view
+     * @sa runtime_view
+     *
+     * @tparam Component Type of components used to construct the view.
+     * @return A newly created standard view.
+     */
+    template<typename... Component>
+    inline entt::view<Entity, Component...> view() const {
+        static_assert(std::conjunction_v<std::is_const<Component>...>);
+        return const_cast<registry *>(this)->view<Component...>();
+    }
+
     /**
      * @brief Returns a persistent view for the given components.
      *
@@ -1188,24 +1228,67 @@ public:
         static_assert(sizeof...(Component) > 1);
         const auto htype = handler_family::type<Component...>;
 
-        if(!(htype < handlers.size() && handlers[htype])) {
-            if(!(htype < handlers.size())) {
-                handlers.resize(htype + 1);
-            }
+        if(!(htype < handlers.size())) {
+            handlers.resize(htype + 1);
+        }
 
-            if(!handlers[htype]) {
-                (assure<Component>(), ...);
-                connect<Component...>(std::make_index_sequence<sizeof...(Component)>{});
-                handlers[htype] = std::make_unique<handler_type<sizeof...(Component)>>();
-            }
+        if(!handlers[htype]) {
+            (assure<Component>(), ...);
+            connect<Component...>(std::make_index_sequence<sizeof...(Component)>{});
+            handlers[htype] = std::make_unique<handler_type<sizeof...(Component)>>();
         }
 
         return {
-            static_cast<handler_type<sizeof...(Component)> *>(handlers[htype].get()),
+            static_cast<handler_type<sizeof...(Component)> *>(handlers[handler_family::type<Component...>].get()),
             &pool<Component>()...
         };
     }
 
+    /**
+     * @brief Returns a persistent view for the given components.
+     *
+     * This kind of views are created on the fly and share with the registry its
+     * internal data structures.<br/>
+     * Feel free to discard a view after the use. Creating and destroying a view
+     * is an incredibly cheap operation because they do not require any type of
+     * initialization.<br/>
+     * As a rule of thumb, storing a view should never be an option.
+     *
+     * Persistent views are the right choice to iterate entities when the number
+     * of components grows up and the most of the entities have all the given
+     * components.<br/>
+     * However they have also drawbacks:
+     *
+     * * Each kind of persistent view requires a dedicated data structure that
+     *   is allocated within the registry and it increases memory pressure.
+     * * Internal data structures used to construct persistent views must be
+     *   kept updated and it affects slightly construction and destruction of
+     *   entities and components.
+     *
+     * That being said, persistent views are an incredibly powerful tool if used
+     * with care and offer a boost of performance undoubtedly.
+     *
+     * @note
+     * Consider to use the `prepare` member function to initialize the internal
+     * data structures used by persistent views when the registry is still
+     * empty. Initialization could be a costly operation otherwise and it will
+     * be performed the very first time each view is created.
+     *
+     * @sa view
+     * @sa view<Entity, Component>
+     * @sa persistent_view
+     * @sa raw_view
+     * @sa runtime_view
+     *
+     * @tparam Component Types of components used to construct the view.
+     * @return A newly created persistent view.
+     */
+    template<typename... Component>
+    inline entt::persistent_view<Entity, Component...> persistent_view() const {
+        static_assert(std::conjunction_v<std::is_const<Component>...>);
+        return const_cast<registry *>(this)->persistent_view<Component...>();
+    }
+
     /**
      * @brief Returns a raw view for the given component.
      *
@@ -1235,6 +1318,35 @@ public:
         return { &pool<Component>() };
     }
 
+    /**
+     * @brief Returns a raw view for the given component.
+     *
+     * This kind of views are created on the fly and share with the registry its
+     * internal data structures.<br/>
+     * Feel free to discard a view after the use. Creating and destroying a view
+     * is an incredibly cheap operation because they do not require any type of
+     * initialization.<br/>
+     * As a rule of thumb, storing a view should never be an option.
+     *
+     * Raw views are incredibly fast and must be considered the best tool to
+     * iterate components whenever knowing the entities to which they belong
+     * isn't required.
+     *
+     * @sa view
+     * @sa view<Entity, Component>
+     * @sa persistent_view
+     * @sa raw_view
+     * @sa runtime_view
+     *
+     * @tparam Component Type of component used to construct the view.
+     * @return A newly created raw view.
+     */
+    template<typename Component>
+    inline entt::raw_view<Entity, Component> raw_view() const {
+        static_assert(std::is_const_v<Component>);
+        return const_cast<registry *>(this)->raw_view<Component>();
+    }
+
     /**
      * @brief Returns a runtime view for the given components.
      *
@@ -1262,7 +1374,7 @@ public:
      * @return A newly created runtime view.
      */
     template<typename It>
-    entt::runtime_view<Entity> runtime_view(It first, It last) {
+    entt::runtime_view<Entity> runtime_view(It first, It last) const {
         static_assert(std::is_convertible_v<typename std::iterator_traits<It>::value_type, component_type>);
         std::vector<const sparse_set<Entity> *> set(last - first);
 
@@ -1340,8 +1452,8 @@ public:
     }
 
 private:
-    std::vector<std::unique_ptr<sparse_set<Entity>>> handlers;
-    std::vector<std::unique_ptr<sparse_set<Entity>>> pools;
+    mutable std::vector<std::unique_ptr<sparse_set<Entity>>> handlers;
+    mutable std::vector<std::unique_ptr<sparse_set<Entity>>> pools;
     std::vector<entity_type> entities;
     size_type available{};
     entity_type next{};

File diff suppressed because it is too large
+ 57 - 627
src/entt/entity/view.hpp


+ 1 - 1
test/entt/entity/snapshot.cpp

@@ -225,7 +225,7 @@ TEST(Snapshot, Iterator) {
     const auto view = registry.view<a_component>();
     const auto size = view.size();
 
-    registry.snapshot().component<another_component>(output, view.cbegin(), view.cend());
+    registry.snapshot().component<another_component>(output, view.begin(), view.end());
     registry.reset();
     registry.loader().component<another_component>(input);
 

+ 109 - 79
test/entt/entity/view.cpp

@@ -7,7 +7,7 @@
 TEST(PersistentView, Functionalities) {
     entt::registry<> registry;
     auto view = registry.persistent_view<int, char>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).persistent_view<const int, const char>();
 
     ASSERT_TRUE(view.empty());
 
@@ -19,8 +19,8 @@ TEST(PersistentView, Functionalities) {
     registry.assign<char>(e1);
 
     ASSERT_FALSE(view.empty());
-    ASSERT_NO_THROW((registry.persistent_view<int, char>().begin()++));
-    ASSERT_NO_THROW((++registry.persistent_view<int, char>().begin()));
+    ASSERT_NO_THROW((view.begin()++));
+    ASSERT_NO_THROW((++cview.begin()));
 
     ASSERT_NE(view.begin(), view.end());
     ASSERT_NE(cview.begin(), cview.end());
@@ -39,10 +39,9 @@ TEST(PersistentView, Functionalities) {
     registry.get<int>(e1) = 42;
 
     for(auto entity: view) {
-        const auto &cview = static_cast<const decltype(view) &>(view);
-        ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
+        ASSERT_EQ(std::get<0>(cview.get<const int, const char>(entity)), 42);
         ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
-        ASSERT_EQ(cview.get<char>(entity), '2');
+        ASSERT_EQ(cview.get<const char>(entity), '2');
     }
 
     ASSERT_EQ(*(view.data() + 0), e1);
@@ -51,14 +50,14 @@ TEST(PersistentView, Functionalities) {
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
-    ASSERT_EQ(view.cbegin(), view.cend());
+    ASSERT_EQ(cview.begin(), cview.end());
     ASSERT_TRUE(view.empty());
 }
 
 TEST(PersistentView, ElementAccess) {
     entt::registry<> registry;
     auto view = registry.persistent_view<int, char>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).persistent_view<const int, const char>();
 
     const auto e0 = registry.create();
     registry.assign<int>(e0);
@@ -127,7 +126,7 @@ TEST(PersistentView, Each) {
     registry.assign<int>(e1);
     registry.assign<char>(e1);
 
-    const auto &cview = static_cast<const decltype(view) &>(view);
+    auto cview = std::as_const(registry).persistent_view<const int, const char>();
     std::size_t cnt = 0;
 
     view.each([&cnt](auto, int &, char &) { ++cnt; });
@@ -141,7 +140,7 @@ TEST(PersistentView, Each) {
 
 TEST(PersistentView, Sort) {
     entt::registry<> registry;
-    auto view = registry.persistent_view<int, unsigned int>();
+    auto view = registry.persistent_view<const int, unsigned int>();
 
     const auto e0 = registry.create();
     const auto e1 = registry.create();
@@ -160,7 +159,7 @@ TEST(PersistentView, Sort) {
 
     for(auto entity: view) {
         ASSERT_EQ(view.get<unsigned int>(entity), --uval);
-        ASSERT_EQ(view.get<int>(entity), --ival);
+        ASSERT_EQ(view.get<const int>(entity), --ival);
     }
 
     registry.sort<unsigned int>(std::less<unsigned int>{});
@@ -168,7 +167,7 @@ TEST(PersistentView, Sort) {
 
     for(auto entity: view) {
         ASSERT_EQ(view.get<unsigned int>(entity), uval++);
-        ASSERT_EQ(view.get<int>(entity), ival++);
+        ASSERT_EQ(view.get<const int>(entity), ival++);
     }
 }
 
@@ -183,7 +182,7 @@ TEST(PersistentView, Initialize) {
 
     registry.assign<int>(registry.create());
 
-    auto view = registry.persistent_view<int, char>();
+    auto view = std::as_const(registry).persistent_view<const int, const char>();
 
     ASSERT_TRUE(view.empty());
     ASSERT_EQ(view.size(), typename decltype(view)::size_type{});
@@ -322,10 +321,26 @@ TEST(PersistentView, IndexRebuiltOnDestroy) {
     });
 }
 
+TEST(PersistentView, ConstNonConstAndAllInBetween) {
+    entt::registry<> registry;
+    auto view = registry.persistent_view<int, const char>();
+
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<int>(0)), int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const int>(0)), const int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const char>(0)), const char &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<int, const char>(0)), std::tuple<int &, const char &>>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const int, const char>(0)), std::tuple<const int &, const char &>>));
+
+    view.each([](auto, auto &&i, auto &&c) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+        ASSERT_TRUE((std::is_same_v<decltype(c), const char &>));
+    });
+}
+
 TEST(SingleComponentView, Functionalities) {
     entt::registry<> registry;
     auto view = registry.view<char>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).view<const char>();
 
     const auto e0 = registry.create();
     const auto e1 = registry.create();
@@ -351,7 +366,6 @@ TEST(SingleComponentView, Functionalities) {
     view.get(e1) = '2';
 
     for(auto entity: view) {
-        const auto &cview = static_cast<const decltype(view) &>(view);
         ASSERT_TRUE(cview.get(entity) == '1' || cview.get(entity) == '2');
     }
 
@@ -365,14 +379,13 @@ TEST(SingleComponentView, Functionalities) {
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
-    ASSERT_EQ(view.cbegin(), view.cend());
     ASSERT_TRUE(view.empty());
 }
 
 TEST(SingleComponentView, ElementAccess) {
     entt::registry<> registry;
     auto view = registry.view<int>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).view<const int>();
 
     const auto e0 = registry.create();
     registry.assign<int>(e0);
@@ -442,10 +455,32 @@ TEST(SingleComponentView, Each) {
     ASSERT_EQ(cnt, std::size_t{0});
 }
 
+TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
+    entt::registry<> registry;
+    auto view = registry.view<int>();
+    auto cview = registry.view<const int>();
+
+    ASSERT_TRUE((std::is_same_v<typename decltype(view)::raw_type, int>));
+    ASSERT_TRUE((std::is_same_v<typename decltype(cview)::raw_type, const int>));
+
+    ASSERT_TRUE((std::is_same_v<decltype(view.get(0)), int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.raw()), int *>));
+    ASSERT_TRUE((std::is_same_v<decltype(cview.get(0)), const int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(cview.raw()), const int *>));
+
+    view.each([](auto, auto &&i) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+    });
+
+    cview.each([](auto, auto &&i) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), const int &>));
+    });
+}
+
 TEST(MultipleComponentView, Functionalities) {
     entt::registry<> registry;
     auto view = registry.view<int, char>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).view<const int, const char>();
 
     ASSERT_TRUE(view.empty());
 
@@ -476,10 +511,9 @@ TEST(MultipleComponentView, Functionalities) {
     registry.get<int>(e1) = 42;
 
     for(auto entity: view) {
-        const auto &cview = static_cast<const decltype(view) &>(view);
-        ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
+        ASSERT_EQ(std::get<0>(cview.get<const int, const char>(entity)), 42);
         ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
-        ASSERT_EQ(cview.get<char>(entity), '2');
+        ASSERT_EQ(cview.get<const char>(entity), '2');
     }
 }
 
@@ -505,28 +539,6 @@ TEST(MultipleComponentView, Iterator) {
     ASSERT_EQ(++view.begin(), view.end());
 }
 
-TEST(MultipleComponentView, ConstIterator) {
-    entt::registry<> registry;
-    const auto entity = registry.create();
-    registry.assign<int>(entity);
-    registry.assign<char>(entity);
-
-    const auto view = registry.view<int, char>();
-    using iterator_type = typename decltype(view)::iterator_type;
-
-    iterator_type cend{view.cbegin()};
-    iterator_type cbegin{};
-    cbegin = view.cend();
-    std::swap(cbegin, cend);
-
-    ASSERT_EQ(cbegin, view.cbegin());
-    ASSERT_EQ(cend, view.cend());
-    ASSERT_NE(cbegin, cend);
-
-    ASSERT_EQ(view.cbegin()++, view.cbegin());
-    ASSERT_EQ(++view.cbegin(), view.cend());
-}
-
 TEST(MultipleComponentView, Contains) {
     entt::registry<> registry;
 
@@ -578,7 +590,7 @@ TEST(MultipleComponentView, Each) {
     registry.assign<char>(e1);
 
     auto view = registry.view<int, char>();
-    const auto &cview = static_cast<const decltype(view) &>(view);
+    auto cview = std::as_const(registry).view<const int, const char>();
     std::size_t cnt = 0;
 
     view.each([&cnt](auto, int &, char &) { ++cnt; });
@@ -615,10 +627,26 @@ TEST(MultipleComponentView, EachWithHoles) {
     });
 }
 
+TEST(MultipleComponentView, ConstNonConstAndAllInBetween) {
+    entt::registry<> registry;
+    auto view = registry.view<int, const char>();
+
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<int>(0)), int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const int>(0)), const int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const char>(0)), const char &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<int, const char>(0)), std::tuple<int &, const char &>>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.get<const int, const char>(0)), std::tuple<const int &, const char &>>));
+
+    view.each([](auto, auto &&i, auto &&c) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+        ASSERT_TRUE((std::is_same_v<decltype(c), const char &>));
+    });
+}
+
 TEST(RawView, Functionalities) {
     entt::registry<> registry;
     auto view = registry.raw_view<char>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).raw_view<const char>();
 
     ASSERT_TRUE(view.empty());
 
@@ -629,8 +657,8 @@ TEST(RawView, Functionalities) {
     registry.assign<char>(e1);
 
     ASSERT_FALSE(view.empty());
-    ASSERT_NO_THROW(registry.raw_view<char>().begin()++);
-    ASSERT_NO_THROW(++registry.raw_view<char>().begin());
+    ASSERT_NO_THROW(view.begin()++);
+    ASSERT_NO_THROW(++cview.begin());
 
     ASSERT_NE(view.begin(), view.end());
     ASSERT_NE(cview.begin(), cview.end());
@@ -651,14 +679,14 @@ TEST(RawView, Functionalities) {
     ASSERT_EQ(*(view.data() + 1), e0);
 
     ASSERT_EQ(*(view.raw() + 0), '2');
-    ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
+    ASSERT_EQ(*(cview.raw() + 1), '1');
 
     for(auto &&component: view) {
         // verifies that iterators return references to components
         component = '0';
     }
 
-    for(auto &&component: view) {
+    for(auto &&component: cview) {
         ASSERT_TRUE(component == '0');
     }
 
@@ -666,14 +694,13 @@ TEST(RawView, Functionalities) {
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
-    ASSERT_EQ(view.cbegin(), view.cend());
     ASSERT_TRUE(view.empty());
 }
 
 TEST(RawView, ElementAccess) {
     entt::registry<> registry;
     auto view = registry.raw_view<int>();
-    const auto &cview = view;
+    auto cview = std::as_const(registry).raw_view<const int>();
 
     const auto e0 = registry.create();
     registry.assign<int>(e0, 42);
@@ -714,7 +741,7 @@ TEST(RawView, Each) {
     registry.assign<int>(registry.create(), 3);
 
     auto view = registry.raw_view<int>();
-    const auto &cview = static_cast<const decltype(view) &>(view);
+    auto cview = std::as_const(registry).raw_view<const int>();
     std::size_t cnt = 0;
 
     view.each([&cnt](int &v) { cnt += (v % 2); });
@@ -726,6 +753,36 @@ TEST(RawView, Each) {
     ASSERT_EQ(cnt, std::size_t{0});
 }
 
+TEST(RawView, ConstNonConstAndAllInBetween) {
+    entt::registry<> registry;
+    auto view = registry.raw_view<int>();
+    auto cview = registry.raw_view<const int>();
+
+    ASSERT_TRUE((std::is_same_v<typename decltype(view)::raw_type, int>));
+    ASSERT_TRUE((std::is_same_v<typename decltype(cview)::raw_type, const int>));
+
+    ASSERT_TRUE((std::is_same_v<decltype(view[0]), int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.raw()), int *>));
+    ASSERT_TRUE((std::is_same_v<decltype(cview[0]), const int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(cview.raw()), const int *>));
+
+    view.each([](auto &&i) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+    });
+
+    cview.each([](auto &&i) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), const int &>));
+    });
+
+    for(auto &&i: view) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+    }
+
+    for(auto &&i: cview) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), const int &>));
+    }
+}
+
 TEST(RuntimeView, Functionalities) {
     entt::registry<> registry;
     using component_type = typename decltype(registry)::component_type;
@@ -736,7 +793,6 @@ TEST(RuntimeView, Functionalities) {
 
     component_type types[] = { registry.type<int>(), registry.type<char>() };
     auto view = registry.runtime_view(std::begin(types), std::end(types));
-    const auto &cview = view;
 
     ASSERT_TRUE(view.empty());
 
@@ -759,7 +815,6 @@ TEST(RuntimeView, Functionalities) {
     ASSERT_NO_THROW((++registry.runtime_view(std::begin(types), std::end(types)).begin()));
 
     ASSERT_NE(view.begin(), view.end());
-    ASSERT_NE(cview.begin(), cview.end());
     ASSERT_EQ(view.size(), decltype(view.size()){1});
 
     registry.get<char>(e0) = '1';
@@ -797,31 +852,6 @@ TEST(RuntimeView, Iterator) {
     ASSERT_EQ(++view.begin(), view.end());
 }
 
-TEST(RuntimeView, ConstIterator) {
-    entt::registry<> registry;
-    using component_type = typename decltype(registry)::component_type;
-
-    const auto entity = registry.create();
-    registry.assign<int>(entity);
-    registry.assign<char>(entity);
-
-    component_type types[] = { registry.type<int>(), registry.type<char>() };
-    auto view = registry.runtime_view(std::begin(types), std::end(types));
-    using iterator_type = typename decltype(view)::iterator_type;
-
-    iterator_type cend{view.cbegin()};
-    iterator_type cbegin{};
-    cbegin = view.cend();
-    std::swap(cbegin, cend);
-
-    ASSERT_EQ(cbegin, view.cbegin());
-    ASSERT_EQ(cend, view.cend());
-    ASSERT_NE(cbegin, cend);
-
-    ASSERT_EQ(view.cbegin()++, view.cbegin());
-    ASSERT_EQ(++view.cbegin(), view.cend());
-}
-
 TEST(RuntimeView, Contains) {
     entt::registry<> registry;
     using component_type = typename decltype(registry)::component_type;

Some files were not shown because too many files changed in this diff