فهرست منبع

instance-less pools for empty components

Michele Caini 6 سال پیش
والد
کامیت
150b83b4f3

+ 51 - 26
src/entt/entity/registry.hpp

@@ -76,10 +76,17 @@ class basic_registry {
         {}
 
         template<typename... Args>
-        Component & assign(basic_registry &registry, const Entity entt, Args &&... args) {
-            auto &component = storage<Entity, Component>::construct(entt, std::forward<Args>(args)...);
-            on_construct.publish(registry, entt, component);
-            return component;
+        decltype(auto) assign(basic_registry &registry, const Entity entt, Args &&... args) {
+            if constexpr(std::is_empty_v<Component>) {
+                Component component{std::forward<Args>(args)...};
+                storage<Entity, Component>::construct(entt);
+                on_construct.publish(registry, entt, component);
+                return std::move(component);
+            } else {
+                auto &component = storage<Entity, Component>::construct(entt, std::forward<Args>(args)...);
+                on_construct.publish(registry, entt, component);
+                return component;
+            }
         }
 
         template<typename It>
@@ -87,9 +94,15 @@ class basic_registry {
             auto *component = storage<Entity, Component>::batch(first, last);
 
             if(!on_construct.empty()) {
-                std::for_each(first, last, [&registry, component, this](const auto entt) mutable {
-                    on_construct.publish(registry, entt, *(component++));
-                });
+                if constexpr(std::is_empty_v<Component>) {
+                    std::for_each(first, last, [&registry, component = Component{}, this](const auto entt) {
+                        on_construct.publish(registry, entt, component);
+                    });
+                } else {
+                    std::for_each(first, last, [&registry, component, this](const auto entt) mutable {
+                        on_construct.publish(registry, entt, *(component++));
+                    });
+                }
             }
 
             return component;
@@ -101,10 +114,15 @@ class basic_registry {
         }
 
         template<typename... Args>
-        Component & replace(basic_registry &registry, const Entity entt, Args &&... args) {
+        decltype(auto) replace(basic_registry &registry, const Entity entt, [[maybe_unused]] Args &&... args) {
             Component component{std::forward<Args>(args)...};
             on_replace.publish(registry, entt, component);
-            return (storage<Entity, Component>::get(entt) = std::move(component));
+
+            if constexpr(std::is_empty_v<Component>) {
+                return std::move(component);
+            } else {
+                return (storage<Entity, Component>::get(entt) = std::move(component));
+            }
         }
     };
 
@@ -145,25 +163,21 @@ class basic_registry {
         void maybe_valid_if(basic_registry &reg, const Entity entt, const Args &...) {
             const auto cpools = std::make_tuple(reg.pool<Owned>()...);
 
-            auto construct = [&cpools, entt, this]() {
-                const auto pos = this->owned++;
-                (std::swap(std::get<pool_type<Owned> *>(cpools)->get(entt), std::get<pool_type<Owned> *>(cpools)->raw()[pos]), ...);
-                (std::get<pool_type<Owned> *>(cpools)->swap(std::get<pool_type<Owned> *>(cpools)->sparse_set<Entity>::get(entt), pos), ...);
-            };
-
             if constexpr(std::disjunction_v<std::is_same<Owned, Component>..., std::is_same<Get, Component>...>) {
                 if(((std::is_same_v<Component, Owned> || std::get<pool_type<Owned> *>(cpools)->has(entt)) && ...)
                         && ((std::is_same_v<Component, Get> || reg.pool<Get>()->has(entt)) && ...)
                         && !(reg.pool<Exclude>()->has(entt) || ...))
                 {
-                    construct();
+                    const auto pos = this->owned++;
+                    (reg.swap<Owned>(0, std::get<pool_type<Owned> *>(cpools), entt, pos), ...);
                 }
             } else if constexpr(std::disjunction_v<std::is_same<Exclude, Component>...>) {
                 if((std::get<pool_type<Owned> *>(cpools)->has(entt) && ...)
                         && (reg.pool<Get>()->has(entt) && ...)
                         && ((std::is_same_v<Exclude, Component> || !reg.pool<Exclude>()->has(entt)) && ...))
                 {
-                    construct();
+                    const auto pos = this->owned++;
+                    (reg.swap<Owned>(0, std::get<pool_type<Owned> *>(cpools), entt, pos), ...);
                 }
             }
         }
@@ -174,8 +188,7 @@ class basic_registry {
 
             if(std::get<0>(cpools)->has(entt) && std::get<0>(cpools)->sparse_set<Entity>::get(entt) < this->owned) {
                 const auto pos = --this->owned;
-                (std::swap(std::get<pool_type<Owned> *>(cpools)->get(entt), std::get<pool_type<Owned> *>(cpools)->raw()[pos]), ...);
-                (std::get<pool_type<Owned> *>(cpools)->swap(std::get<pool_type<Owned> *>(cpools)->sparse_set<Entity>::get(entt), pos), ...);
+                (reg.swap<Owned>(0, std::get<pool_type<Owned> *>(cpools), entt, pos), ...);
             }
         }
     };
@@ -217,6 +230,18 @@ class basic_registry {
         ++available;
     }
 
+    template<typename Component>
+    inline auto swap(int, pool_type<Component> *cpool, const Entity entt, const std::size_t pos)
+    -> decltype(std::swap(cpool->get(entt), cpool->get(cpool->data()[pos])), void()) {
+        std::swap(cpool->get(entt), cpool->get(cpool->data()[pos]));
+        cpool->swap(cpool->sparse_set<Entity>::get(entt), pos);
+    }
+
+    template<typename Component>
+    inline void swap(char, pool_type<Component> *cpool, const Entity entt, const std::size_t pos) {
+        cpool->swap(cpool->sparse_set<Entity>::get(entt), pos);
+    }
+
     template<typename Component>
     inline const auto * pool() const ENTT_NOEXCEPT {
         const auto ctype = type<Component>();
@@ -694,7 +719,7 @@ public:
      * @return A reference to the newly created component.
      */
     template<typename Component, typename... Args>
-    Component & assign(const entity_type entity, Args &&... args) {
+    decltype(auto) assign(const entity_type entity, [[maybe_unused]] Args &&... args) {
         ENTT_ASSERT(valid(entity));
         return assure<Component>()->assign(*this, entity, std::forward<Args>(args)...);
     }
@@ -797,7 +822,7 @@ public:
      * @return Reference to the component owned by the entity.
      */
     template<typename Component, typename... Args>
-    Component & get_or_assign(const entity_type entity, Args &&... args) ENTT_NOEXCEPT {
+    decltype(auto) get_or_assign(const entity_type entity, Args &&... args) ENTT_NOEXCEPT {
         ENTT_ASSERT(valid(entity));
         auto *cpool = assure<Component>();
         auto *comp = cpool->try_get(entity);
@@ -859,7 +884,7 @@ public:
      * @return A reference to the newly created component.
      */
     template<typename Component, typename... Args>
-    Component & replace(const entity_type entity, Args &&... args) {
+    decltype(auto) replace(const entity_type entity, Args &&... args) {
         return pool<Component>()->replace(*this, entity, std::forward<Args>(args)...);
     }
 
@@ -886,7 +911,7 @@ public:
      * @return A reference to the newly created component.
      */
     template<typename Component, typename... Args>
-    Component & assign_or_replace(const entity_type entity, Args &&... args) {
+    decltype(auto) assign_or_replace(const entity_type entity, Args &&... args) {
         auto *cpool = assure<Component>();
         return cpool->has(entity) ? cpool->replace(*this, entity, std::forward<Args>(args)...) : cpool->assign(*this, entity, std::forward<Args>(args)...);
     }
@@ -1290,6 +1315,7 @@ public:
     inline entt::basic_group<Entity, get_t<Get...>, Owned...> group(get_t<Get...>, exclude_t<Exclude...> = {}) {
         static_assert(sizeof...(Owned) + sizeof...(Get) > 0);
         static_assert(sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude) > 1);
+
         using handler_type = group_handler<type_list<Exclude...>, type_list<Get...>, Owned...>;
 
         const std::size_t extent[] = { sizeof...(Owned), sizeof...(Get), sizeof...(Exclude) };
@@ -1336,7 +1362,7 @@ public:
             });
 
             // we cannot iterate backwards because we want to leave behind valid entities in case of owned types
-            std::for_each(cpool->data(), cpool->data() + cpool->size(), [curr, &cpools](const auto entity) {
+            std::for_each(cpool->data(), cpool->data() + cpool->size(), [curr, &cpools, this](const auto entity) {
                 if((std::get<pool_type<Owned> *>(cpools)->has(entity) && ...)
                         && (std::get<pool_type<Get> *>(cpools)->has(entity) && ...)
                         && !(std::get<pool_type<Exclude> *>(cpools)->has(entity) || ...))
@@ -1345,8 +1371,7 @@ public:
                         curr->construct(entity);
                     } else {
                         const auto pos = curr->owned++;
-                        (std::swap(std::get<pool_type<Owned> *>(cpools)->get(entity), std::get<pool_type<Owned> *>(cpools)->raw()[pos]), ...);
-                        (std::get<pool_type<Owned> *>(cpools)->swap(std::get<pool_type<Owned> *>(cpools)->sparse_set<Entity>::get(entity), pos), ...);
+                        (swap<Owned>(0, std::get<pool_type<Owned> *>(cpools), entity, pos), ...);
                     }
                 }
             });

+ 15 - 17
src/entt/entity/snapshot.hpp

@@ -226,18 +226,19 @@ class basic_snapshot_loader {
         archive(length);
 
         while(length--) {
+            static constexpr auto destroyed = false;
             Entity entt{};
-            Type instance{};
 
             if constexpr(std::is_empty_v<Type>) {
                 archive(entt);
+                force(*reg, entt, destroyed);
+                reg->template assign<Type>(args..., entt);
             } else {
+                Type instance{};
                 archive(entt, instance);
+                force(*reg, entt, destroyed);
+                reg->template assign<Type>(args..., entt, std::as_const(instance));
             }
-
-            static constexpr auto destroyed = false;
-            force(*reg, entt, destroyed);
-            reg->template assign<Type>(args..., entt, std::as_const(instance));
         }
     }
 
@@ -405,24 +406,25 @@ class basic_continuous_loader {
         }
     }
 
-    template<typename Other, typename Archive, typename Func, typename... Type, typename... Member>
-    void assign(Archive &archive, Func func, Member Type:: *... member) {
+    template<typename Other, typename Archive, typename... Type, typename... Member>
+    void assign(Archive &archive, [[maybe_unused]] Member Type:: *... member) {
         Entity length{};
         archive(length);
 
         while(length--) {
             Entity entt{};
-            Other instance{};
 
             if constexpr(std::is_empty_v<Other>) {
                 archive(entt);
+                restore(entt);
+                reg->template assign_or_replace<Other>(map(entt));
             } else {
+                Other instance{};
                 archive(entt, instance);
+                (update(instance, member), ...);
+                restore(entt);
+                reg->template assign_or_replace<Other>(map(entt), std::as_const(instance));
             }
-
-            restore(entt);
-            (update(instance, member), ...);
-            func(map(entt), instance);
         }
     }
 
@@ -497,12 +499,8 @@ public:
      */
     template<typename... Component, typename Archive, typename... Type, typename... Member>
     basic_continuous_loader & component(Archive &archive, Member Type:: *... member) {
-        auto apply = [this](const auto entt, const auto &component) {
-            reg->template assign_or_replace<std::decay_t<decltype(component)>>(entt, component);
-        };
-
         (reset<Component>(), ...);
-        (assign<Component>(archive, apply, member...), ...);
+        (assign<Component>(archive, member...), ...);
         return *this;
     }
 

+ 3 - 3
src/entt/entity/sparse_set.hpp

@@ -61,9 +61,9 @@ class sparse_set {
 
     public:
         using difference_type = index_type;
-        using value_type = const Entity;
-        using pointer = value_type *;
-        using reference = value_type &;
+        using value_type = Entity;
+        using pointer = const value_type *;
+        using reference = const value_type &;
         using iterator_category = std::random_access_iterator_tag;
 
         iterator() ENTT_NOEXCEPT = default;

+ 268 - 214
src/entt/entity/storage.hpp

@@ -22,7 +22,7 @@ namespace entt {
  * @brief Basic storage implementation.
  *
  * This class is a refinement of a sparse set that associates an object to an
- * entity. The main purpose of this class is to use sparse sets to store
+ * entity. The main purpose of this class is to extend sparse sets to store
  * components in a registry. It guarantees fast access both to the elements and
  * to the entities.
  *
@@ -36,19 +36,24 @@ namespace entt {
  * iterate directly the internal packed array (see `raw` and `size` member
  * functions for that). Use `begin` and `end` instead.
  *
+ * @warning
+ * Empty types aren't explicitly instantiated. Temporary objects are returned in
+ * place of the instances of the components and raw access isn't available for
+ * them.
+ *
  * @sa sparse_set<Entity>
  *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Type Type of objects assigned to the entities.
  */
-template<typename Entity, typename Type>
-class storage: public sparse_set<Entity> {
+template<typename Entity, typename Type, typename = std::void_t<>>
+class basic_storage: public sparse_set<Entity> {
     using underlying_type = sparse_set<Entity>;
     using traits_type = entt_traits<Entity>;
 
-    template<bool Const, bool = std::is_empty_v<Type>>
+    template<bool Const>
     class iterator {
-        friend class storage<Entity, Type>;
+        friend class basic_storage<Entity, Type>;
 
         using instance_type = std::conditional_t<Const, const std::vector<Type>, std::vector<Type>>;
         using index_type = typename traits_type::difference_type;
@@ -59,9 +64,9 @@ class storage: public sparse_set<Entity> {
 
     public:
         using difference_type = index_type;
-        using value_type = std::conditional_t<Const, const Type, Type>;
-        using pointer = value_type *;
-        using reference = value_type &;
+        using value_type = Type;
+        using pointer = std::conditional_t<Const, const value_type *, value_type *>;
+        using reference = std::conditional_t<Const, const value_type &, value_type &>;
         using iterator_category = std::random_access_iterator_tag;
 
         iterator() ENTT_NOEXCEPT = default;
@@ -148,106 +153,6 @@ class storage: public sparse_set<Entity> {
         index_type index;
     };
 
-    template<bool Const>
-    class iterator<Const, true> {
-        friend class storage<Entity, Type>;
-
-        using instance_type = std::conditional_t<Const, const Type, Type>;
-        using index_type = typename traits_type::difference_type;
-
-        iterator(instance_type *ref, const index_type idx) ENTT_NOEXCEPT
-            : instance{ref}, index{idx}
-        {}
-
-    public:
-        using difference_type = index_type;
-        using value_type = std::conditional_t<Const, const Type, Type>;
-        using pointer = value_type *;
-        using reference = value_type &;
-        using iterator_category = std::random_access_iterator_tag;
-
-        iterator() ENTT_NOEXCEPT = default;
-
-        iterator & operator++() ENTT_NOEXCEPT {
-            return --index, *this;
-        }
-
-        iterator operator++(int) ENTT_NOEXCEPT {
-            iterator orig = *this;
-            return ++(*this), orig;
-        }
-
-        iterator & operator--() ENTT_NOEXCEPT {
-            return ++index, *this;
-        }
-
-        iterator operator--(int) ENTT_NOEXCEPT {
-            iterator orig = *this;
-            return --(*this), orig;
-        }
-
-        iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
-            index -= value;
-            return *this;
-        }
-
-        iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
-            return iterator{instance, index-value};
-        }
-
-        inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
-            return (*this += -value);
-        }
-
-        inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
-            return (*this + -value);
-        }
-
-        difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
-            return other.index - index;
-        }
-
-        reference operator[](const difference_type) const ENTT_NOEXCEPT {
-            return *instance;
-        }
-
-        bool operator==(const iterator &other) const ENTT_NOEXCEPT {
-            return other.index == index;
-        }
-
-        inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
-            return !(*this == other);
-        }
-
-        bool operator<(const iterator &other) const ENTT_NOEXCEPT {
-            return index > other.index;
-        }
-
-        bool operator>(const iterator &other) const ENTT_NOEXCEPT {
-            return index < other.index;
-        }
-
-        inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
-            return !(*this > other);
-        }
-
-        inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
-            return !(*this < other);
-        }
-
-        pointer operator->() const ENTT_NOEXCEPT {
-            return instance;
-        }
-
-        inline reference operator*() const ENTT_NOEXCEPT {
-            return *operator->();
-        }
-
-    private:
-        instance_type *instance;
-        index_type index;
-    };
-
 public:
     /*! @brief Type of the objects associated with the entities. */
     using object_type = Type;
@@ -270,25 +175,13 @@ public:
      */
     void reserve(const size_type cap) override {
         underlying_type::reserve(cap);
-
-        if constexpr(!std::is_empty_v<object_type>) {
-            instances.reserve(cap);
-        }
+        instances.reserve(cap);
     }
 
-    /**
-     * @brief Requests the removal of unused capacity.
-     *
-     * @note
-     * Empty components aren't explicitly instantiated. Only one instance of the
-     * given type is created. Therefore, this function does nothing.
-     */
+    /*! @brief Requests the removal of unused capacity. */
     void shrink_to_fit() override {
         underlying_type::shrink_to_fit();
-
-        if constexpr(!std::is_empty_v<object_type>) {
-            instances.shrink_to_fit();
-        }
+        instances.shrink_to_fit();
     }
 
     /**
@@ -304,19 +197,10 @@ public:
      * performance boost but less guarantees. Use `begin` and `end` if you want
      * to iterate the storage in the expected order.
      *
-     * @note
-     * Empty components aren't explicitly instantiated. Only one instance of the
-     * given type is created. Therefore, this function always returns a pointer
-     * to that instance.
-     *
      * @return A pointer to the array of objects.
      */
     const object_type * raw() const ENTT_NOEXCEPT {
-        if constexpr(std::is_empty_v<object_type>) {
-            return &instances;
-        } else {
-            return instances.data();
-        }
+        return instances.data();
     }
 
     /*! @copydoc raw */
@@ -392,13 +276,8 @@ public:
      * @param entt A valid entity identifier.
      * @return The object associated with the entity.
      */
-    const object_type & get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
-        if constexpr(std::is_empty_v<object_type>) {
-            ENTT_ASSERT(underlying_type::has(entt));
-            return instances;
-        } else {
-            return instances[underlying_type::get(entt)];
-        }
+    const object_type & get(const entity_type entt) const ENTT_NOEXCEPT {
+        return instances[underlying_type::get(entt)];
     }
 
     /*! @copydoc get */
@@ -412,11 +291,7 @@ public:
      * @return The object associated with the entity, if any.
      */
     const object_type * try_get(const entity_type entt) const ENTT_NOEXCEPT {
-        if constexpr(std::is_empty_v<object_type>) {
-            return underlying_type::has(entt) ? &instances : nullptr;
-        } else {
-            return underlying_type::has(entt) ? (instances.data() + underlying_type::get(entt)) : nullptr;
-        }
+        return underlying_type::has(entt) ? (instances.data() + underlying_type::get(entt)) : nullptr;
     }
 
     /*! @copydoc try_get */
@@ -443,21 +318,16 @@ public:
      * @return The object associated with the entity.
      */
     template<typename... Args>
-    object_type & construct(const entity_type entt, [[maybe_unused]] Args &&... args) {
-        if constexpr(std::is_empty_v<object_type>) {
-            underlying_type::construct(entt);
-            return instances;
+    object_type & construct(const entity_type entt, Args &&... args) {
+        if constexpr(std::is_aggregate_v<object_type>) {
+            instances.emplace_back(Type{std::forward<Args>(args)...});
         } else {
-            if constexpr(std::is_aggregate_v<object_type>) {
-                instances.emplace_back(Type{std::forward<Args>(args)...});
-            } else {
-                instances.emplace_back(std::forward<Args>(args)...);
-            }
-
-            // entity goes after component in case constructor throws
-            underlying_type::construct(entt);
-            return instances.back();
+            instances.emplace_back(std::forward<Args>(args)...);
         }
+
+        // entity goes after component in case constructor throws
+        underlying_type::construct(entt);
+        return instances.back();
     }
 
     /**
@@ -466,11 +336,6 @@ public:
      *
      * The object type must be at least default constructible.
      *
-     * @note
-     * Empty components aren't explicitly instantiated. Only one instance of the
-     * given type is created. Therefore, this function always returns a pointer
-     * to that instance.
-     *
      * @warning
      * Attempting to assign an entity that already belongs to the storage
      * results in undefined behavior.<br/>
@@ -485,17 +350,12 @@ public:
      */
     template<typename It>
     object_type * batch(It first, It last) {
-        if constexpr(std::is_empty_v<object_type>) {
-            underlying_type::batch(first, last);
-            return &instances;
-        } else {
-            static_assert(std::is_default_constructible_v<object_type>);
-            const auto skip = instances.size();
-            instances.insert(instances.end(), last-first, {});
-            // entity goes after component in case constructor throws
-            underlying_type::batch(first, last);
-            return instances.data() + skip;
-        }
+        static_assert(std::is_default_constructible_v<object_type>);
+        const auto skip = instances.size();
+        instances.insert(instances.end(), last-first, {});
+        // entity goes after component in case constructor throws
+        underlying_type::batch(first, last);
+        return instances.data() + skip;
     }
 
     /**
@@ -510,16 +370,13 @@ public:
      * @param entt A valid entity identifier.
      */
     void destroy(const entity_type entt) override {
-        if constexpr(!std::is_empty_v<object_type>) {
-            std::swap(instances[underlying_type::get(entt)], instances.back());
-            instances.pop_back();
-        }
-
+        std::swap(instances[underlying_type::get(entt)], instances.back());
+        instances.pop_back();
         underlying_type::destroy(entt);
     }
 
     /**
-     * @brief Sort components according to the given comparison function.
+     * @brief Sort instances according to the given comparison function.
      *
      * Sort the elements so that iterating the storage with a couple of
      * iterators returns them in the expected order. See `begin` and `end` for
@@ -549,10 +406,6 @@ public:
      * parameters to this member function.
      *
      * @note
-     * Empty components aren't explicitly instantiated. Therefore, the
-     * comparison function must necessarily accept entity identifiers.
-     *
-     * @note
      * Attempting to iterate elements using a raw pointer returned by a call to
      * either `data` or `raw` gives no guarantees on the order, even though
      * `sort` has been invoked.
@@ -588,11 +441,7 @@ public:
             while(curr != next) {
                 const auto lhs = copy[curr];
                 const auto rhs = copy[next];
-
-                if constexpr(!std::is_empty_v<object_type>) {
-                    std::swap(instances[lhs], instances[rhs]);
-                }
-
+                std::swap(instances[lhs], instances[rhs]);
                 underlying_type::swap(lhs, rhs);
                 copy[curr] = curr;
                 curr = next;
@@ -602,14 +451,13 @@ public:
     }
 
     /**
-     * @brief Sort components according to the order of the entities in another
+     * @brief Sort instances according to the order of the entities in another
      * sparse set.
      *
      * Entities that are part of both the storage are ordered internally
      * according to the order they have in `other`. All the other entities goes
      * to the end of the list and there are no guarantess on their order.
-     * Components are sorted according to the entities to which they
-     * belong.<br/>
+     * Instances are sorted according to the entities to which they belong.<br/>
      * In other terms, this function can be used to impose the same order on two
      * sets by using one of them as a master and the other one as a slave.
      *
@@ -625,46 +473,252 @@ public:
      * @param other The sparse sets that imposes the order of the entities.
      */
     void respect(const sparse_set<Entity> &other) ENTT_NOEXCEPT override {
-        if constexpr(std::is_empty_v<object_type>) {
-            underlying_type::respect(other);
-        } else {
-            const auto to = other.end();
-            auto from = other.begin();
-
-            size_type pos = underlying_type::size() - 1;
-            const auto *local = underlying_type::data();
+        const auto to = other.end();
+        auto from = other.begin();
 
-            while(pos && from != to) {
-                const auto curr = *from;
+        size_type pos = underlying_type::size() - 1;
+        const auto *local = underlying_type::data();
 
-                if(underlying_type::has(curr)) {
-                    if(curr != *(local + pos)) {
-                        auto candidate = underlying_type::get(curr);
-                        std::swap(instances[pos], instances[candidate]);
-                        underlying_type::swap(pos, candidate);
-                    }
+        while(pos && from != to) {
+            const auto curr = *from;
 
-                    --pos;
+            if(underlying_type::has(curr)) {
+                if(curr != *(local + pos)) {
+                    auto candidate = underlying_type::get(curr);
+                    std::swap(instances[pos], instances[candidate]);
+                    underlying_type::swap(pos, candidate);
                 }
 
-                ++from;
+                --pos;
             }
+
+            ++from;
         }
     }
 
     /*! @brief Resets a storage. */
     void reset() override {
         underlying_type::reset();
+        instances.clear();
+    }
+
+private:
+    std::vector<object_type> instances;
+};
+
+
+/*! @copydoc basic_storage */
+template<typename Entity, typename Type>
+class basic_storage<Entity, Type, std::enable_if_t<std::is_empty_v<Type>>>: public sparse_set<Entity> {
+    using underlying_type = sparse_set<Entity>;
+    using traits_type = entt_traits<Entity>;
+
+    class iterator {
+        friend class basic_storage<Entity, Type>;
+
+        using index_type = typename traits_type::difference_type;
+
+        iterator(const index_type idx) ENTT_NOEXCEPT
+            : index{idx}
+        {}
+
+    public:
+        using difference_type = index_type;
+        using value_type = Type;
+        using pointer = const value_type *;
+        using reference = value_type;
+        using iterator_category = std::input_iterator_tag;
 
-        if constexpr(!std::is_empty_v<object_type>) {
-            instances.clear();
+        iterator() ENTT_NOEXCEPT = default;
+
+        iterator & operator++() ENTT_NOEXCEPT {
+            return --index, *this;
+        }
+
+        iterator operator++(int) ENTT_NOEXCEPT {
+            iterator orig = *this;
+            return ++(*this), orig;
         }
+
+        iterator & operator--() ENTT_NOEXCEPT {
+            return ++index, *this;
+        }
+
+        iterator operator--(int) ENTT_NOEXCEPT {
+            iterator orig = *this;
+            return --(*this), orig;
+        }
+
+        iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
+            index -= value;
+            return *this;
+        }
+
+        iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
+            return iterator{index-value};
+        }
+
+        inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
+            return (*this += -value);
+        }
+
+        inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
+            return (*this + -value);
+        }
+
+        difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
+            return other.index - index;
+        }
+
+        reference operator[](const difference_type) const ENTT_NOEXCEPT {
+            return {};
+        }
+
+        bool operator==(const iterator &other) const ENTT_NOEXCEPT {
+            return other.index == index;
+        }
+
+        inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
+            return !(*this == other);
+        }
+
+        bool operator<(const iterator &other) const ENTT_NOEXCEPT {
+            return index > other.index;
+        }
+
+        bool operator>(const iterator &other) const ENTT_NOEXCEPT {
+            return index < other.index;
+        }
+
+        inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
+            return !(*this > other);
+        }
+
+        inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
+            return !(*this < other);
+        }
+
+        pointer operator->() const ENTT_NOEXCEPT {
+            return nullptr;
+        }
+
+        inline reference operator*() const ENTT_NOEXCEPT {
+            return {};
+        }
+
+    private:
+        index_type index;
+    };
+
+public:
+    /*! @brief Type of the objects associated with the entities. */
+    using object_type = Type;
+    /*! @brief Underlying entity identifier. */
+    using entity_type = typename underlying_type::entity_type;
+    /*! @brief Unsigned integer type. */
+    using size_type = typename underlying_type::size_type;
+    /*! @brief Random access iterator type. */
+    using iterator_type = iterator;
+
+    /**
+     * @brief Direct access to the array of objects.
+     *
+     * @note
+     * Empty types aren't explicitly instantiated. Therefore, this function
+     * always returns a null pointer.
+     *
+     * @return A pointer to the array of objects.
+     */
+    const object_type * raw() const ENTT_NOEXCEPT {
+        return nullptr;
     }
 
-private:
-    std::conditional_t<std::is_empty_v<object_type>, object_type, std::vector<object_type>> instances;
+    /**
+     * @brief Returns an iterator to the beginning.
+     *
+     * The returned iterator points to the first instance of the given type. If
+     * the storage is empty, the returned iterator will be equal to `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed by a call to either `sort`
+     * or `respect`.
+     *
+     * @return An iterator to the first instance of the given type.
+     */
+    iterator_type cbegin() const ENTT_NOEXCEPT {
+        const typename traits_type::difference_type pos = underlying_type::size();
+        return iterator_type{pos};
+    }
+
+    /*! @copydoc cbegin */
+    inline iterator_type begin() const ENTT_NOEXCEPT {
+        return cbegin();
+    }
+
+    /**
+     * @brief Returns an iterator to the end.
+     *
+     * The returned iterator points to the element following the last instance
+     * of the given type. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed by a call to either `sort`
+     * or `respect`.
+     *
+     * @return An iterator to the element following the last instance of the
+     * given type.
+     */
+    iterator_type cend() const ENTT_NOEXCEPT {
+        return iterator_type{};
+    }
+
+    /*! @copydoc cend */
+    inline iterator_type end() const ENTT_NOEXCEPT {
+        return cend();
+    }
+
+    /**
+     * @brief Returns the object associated with an entity.
+     *
+     * @note
+     * Empty types aren't explicitly instantiated. Therefore, this function
+     * always returns a temporary object.
+     *
+     * @warning
+     * Attempting to use an entity that doesn't belong to the storage results in
+     * undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * storage doesn't contain the given entity.
+     *
+     * @param entt A valid entity identifier.
+     * @return The object associated with the entity.
+     */
+    object_type get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
+        ENTT_ASSERT(underlying_type::has(entt));
+        return {};
+    }
+
+    /**
+     * @brief Returns a pointer to the object associated with an entity, if any.
+     *
+     * @note
+     * Empty types aren't explicitly instantiated. Therefore, this function
+     * always returns a null pointer.
+     *
+     * @param entt A valid entity identifier.
+     * @return The object associated with the entity, if any.
+     */
+    const object_type * try_get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
+        ENTT_ASSERT(underlying_type::has(entt));
+        return nullptr;
+    }
 };
 
+/*! @copydoc basic_storage */
+template<typename Entity, typename Type>
+struct storage: basic_storage<Entity, Type> {};
+
 
 }
 

+ 14 - 6
src/entt/entity/view.hpp

@@ -161,20 +161,28 @@ class basic_view {
         return other;
     }
 
+    template<typename Comp, typename Other>
+    inline decltype(auto) get([[maybe_unused]] component_iterator_type<Comp> it, [[maybe_unused]] pool_type<Other> *cpool, [[maybe_unused]] const Entity entt) const ENTT_NOEXCEPT {
+        if constexpr(std::is_same_v<Comp, Other>) {
+            return *it;
+        } else {
+            return cpool->get(entt);
+        }
+    }
+
     template<typename Comp, typename... Other, typename Func>
     void each(type_list<Other...>, Func func) const {
         const auto end = std::get<pool_type<Comp> *>(pools)->sparse_set<Entity>::end();
         auto begin = std::get<pool_type<Comp> *>(pools)->sparse_set<Entity>::begin();
 
-        std::for_each(begin, end, [&func, raw = std::get<pool_type<Comp> *>(pools)->begin(), this](const auto entity) mutable {
-            std::tuple<component_iterator_type<Comp>, Other *...> pack;
-            std::get<component_iterator_type<Comp>>(pack) = raw++;
+        std::for_each(begin, end, [raw = std::get<pool_type<Comp> *>(pools)->begin(), &func, this](const auto entity) mutable {
+            auto curr = raw++;
 
-            if(((std::get<Other *>(pack) = std::get<pool_type<Other> *>(pools)->try_get(entity)) && ...)) {
+            if((std::get<pool_type<Other> *>(pools)->has(entity) && ...)) {
                 if constexpr(std::is_invocable_v<Func, std::add_lvalue_reference_t<Component>...>) {
-                    func(*std::get<std::conditional_t<std::is_same_v<Comp, Component>, component_iterator_type<Component>, Component *>>(pack)...);
+                    func(get<Comp, Component>(curr, std::get<pool_type<Component> *>(pools), entity)...);
                 } else {
-                    func(entity, *std::get<std::conditional_t<std::is_same_v<Comp, Component>, component_iterator_type<Component>, Component *>>(pack)...);
+                    func(entity, get<Comp, Component>(curr, std::get<pool_type<Component> *>(pools), entity)...);
                 }
             }
         });

+ 1 - 2
src/entt/meta/factory.hpp

@@ -131,8 +131,7 @@ class meta_factory {
 
     template<auto Member>
     auto unregister_all(int)
-    -> decltype((internal::meta_info<Type>::type->*Member)->prop, void())
-    {
+    -> decltype((internal::meta_info<Type>::type->*Member)->prop, void()) {
         while(internal::meta_info<Type>::type->*Member) {
             auto node = internal::meta_info<Type>::type->*Member;
             internal::meta_info<Type>::type->*Member = node->next;

+ 2 - 4
src/entt/meta/meta.hpp

@@ -222,8 +222,7 @@ auto find_if(Op op, const Node *curr) ENTT_NOEXCEPT {
 
 template<auto Member, typename Op>
 auto find_if(Op op, const meta_type_node *node) ENTT_NOEXCEPT
--> decltype(find_if(op, node->*Member))
-{
+-> decltype(find_if(op, node->*Member)) {
     decltype(find_if(op, node->*Member)) ret = nullptr;
 
     if(node) {
@@ -311,8 +310,7 @@ class meta_any {
 
     template<typename Type>
     inline static auto compare(int, const Type &lhs, const Type &rhs)
-    -> decltype(lhs == rhs, bool{})
-    {
+    -> decltype(lhs == rhs, bool{}) {
         return lhs == rhs;
     }
 

+ 2 - 4
test/entt/entity/group.cpp

@@ -366,12 +366,11 @@ TEST(NonOwningGroup, EmptyAndNonEmptyTypes) {
         ASSERT_TRUE(entity == e0 || entity == e1);
     }
 
-    group.each([e0, e1](const auto entity, const int &, const empty_type &) {
+    group.each([e0, e1](const auto entity, const int &, empty_type) {
         ASSERT_TRUE(entity == e0 || entity == e1);
     });
 
     ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
-    ASSERT_EQ(&group.get<empty_type>(e0), &group.get<empty_type>(e1));
 }
 
 TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) {
@@ -872,12 +871,11 @@ TEST(OwningGroup, EmptyAndNonEmptyTypes) {
         ASSERT_TRUE(entity == e0 || entity == e1);
     }
 
-    group.each([e0, e1](const auto entity, const empty_type &, const int &) {
+    group.each([e0, e1](const auto entity, empty_type, const int &) {
         ASSERT_TRUE(entity == e0 || entity == e1);
     });
 
     ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
-    ASSERT_EQ(&group.get<empty_type>(e0), &group.get<empty_type>(e1));
 }
 
 TEST(OwningGroup, TrackEntitiesOnComponentDestruction) {

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

@@ -248,7 +248,6 @@ TEST(Snapshot, Continuous) {
 
     using storage_type = std::tuple<
         std::queue<entt::entity>,
-        std::queue<a_component>,
         std::queue<another_component>,
         std::queue<what_a_component>,
         std::queue<double>
@@ -432,10 +431,7 @@ TEST(Snapshot, MoreOnShrink) {
 
     entt::continuous_loader loader{dst};
 
-    using storage_type = std::tuple<
-        std::queue<entt::entity>,
-        std::queue<a_component>
-    >;
+    using storage_type = std::tuple<std::queue<entt::entity>>;
 
     storage_type storage;
     output_archive<storage_type> output{storage};

+ 42 - 83
test/entt/entity/storage.cpp

@@ -19,7 +19,7 @@ struct throwing_component {
     int data;
 };
 
-TEST(SparseSetWithType, Functionalities) {
+TEST(Storage, Functionalities) {
     entt::storage<std::uint64_t, int> set;
 
     set.reserve(42);
@@ -79,16 +79,23 @@ TEST(SparseSetWithType, Functionalities) {
     other = std::move(set);
 }
 
-TEST(SparseSetWithType, EmptyType) {
+TEST(Storage, EmptyType) {
     entt::storage<std::uint64_t, empty_type> set;
 
-    ASSERT_EQ(&set.construct(42), &set.construct(99));
-    ASSERT_EQ(&set.get(42), set.try_get(42));
-    ASSERT_EQ(&set.get(42), &set.get(99));
+    set.construct(42);
+    set.construct(99);
+
+    ASSERT_TRUE(set.has(42));
+    ASSERT_TRUE(set.has(99));
+    ASSERT_EQ(set.try_get(42), nullptr);
     ASSERT_EQ(std::as_const(set).try_get(42), std::as_const(set).try_get(99));
+
+    auto &&component = set.get(42);
+
+    ASSERT_TRUE((std::is_same_v<decltype(component), empty_type &&>));
 }
 
-TEST(SparseSetWithType, BatchAdd) {
+TEST(Storage, BatchAdd) {
     entt::storage<std::uint64_t, int> set;
     entt::storage<std::uint64_t, int>::entity_type entities[2];
 
@@ -121,7 +128,7 @@ TEST(SparseSetWithType, BatchAdd) {
     ASSERT_EQ(set.get(entities[1]), 2);
 }
 
-TEST(SparseSetWithType, BatchAddEmptyType) {
+TEST(Storage, BatchAddEmptyType) {
     entt::storage<std::uint64_t, empty_type> set;
     entt::storage<std::uint64_t, empty_type>::entity_type entities[2];
 
@@ -130,7 +137,7 @@ TEST(SparseSetWithType, BatchAddEmptyType) {
 
     set.reserve(4);
     set.construct(12);
-    auto *component = set.batch(std::begin(entities), std::end(entities));
+    set.batch(std::begin(entities), std::end(entities));
     set.construct(24);
 
     ASSERT_TRUE(set.has(entities[0]));
@@ -142,26 +149,26 @@ TEST(SparseSetWithType, BatchAddEmptyType) {
 
     ASSERT_FALSE(set.empty());
     ASSERT_EQ(set.size(), 4u);
-    ASSERT_EQ(&set.get(entities[0]), &set.get(entities[1]));
-    ASSERT_EQ(&set.get(entities[0]), &set.get(12));
-    ASSERT_EQ(&set.get(entities[0]), &set.get(24));
-    ASSERT_EQ(&set.get(entities[0]), component);
+
+    auto &&component = set.get(entities[0]);
+
+    ASSERT_TRUE((std::is_same_v<decltype(component), empty_type &&>));
 }
 
-TEST(SparseSetWithType, AggregatesMustWork) {
+TEST(Storage, AggregatesMustWork) {
     struct aggregate_type { int value; };
     // the goal of this test is to enforce the requirements for aggregate types
     entt::storage<std::uint64_t, aggregate_type>{}.construct(0, 42);
 }
 
-TEST(SparseSetWithType, TypesFromStandardTemplateLibraryMustWork) {
+TEST(Storage, TypesFromStandardTemplateLibraryMustWork) {
     // see #37 - this test shouldn't crash, that's all
     entt::storage<std::uint64_t, std::unordered_set<int>> set;
     set.construct(0).insert(42);
     set.destroy(0);
 }
 
-TEST(SparseSetWithType, Iterator) {
+TEST(Storage, Iterator) {
     using iterator_type = typename entt::storage<std::uint64_t, boxed_int>::iterator_type;
 
     entt::storage<std::uint64_t, boxed_int> set;
@@ -203,7 +210,7 @@ TEST(SparseSetWithType, Iterator) {
     ASSERT_GE(end, set.end());
 }
 
-TEST(SparseSetWithType, ConstIterator) {
+TEST(Storage, ConstIterator) {
     using iterator_type = typename entt::storage<std::uint64_t, boxed_int>::const_iterator_type;
 
     entt::storage<std::uint64_t, boxed_int> set;
@@ -245,7 +252,7 @@ TEST(SparseSetWithType, ConstIterator) {
     ASSERT_GE(cend, set.cend());
 }
 
-TEST(SparseSetWithType, IteratorEmptyType) {
+TEST(Storage, IteratorEmptyType) {
     using iterator_type = typename entt::storage<std::uint64_t, empty_type>::iterator_type;
     entt::storage<std::uint64_t, empty_type> set;
     set.construct(3);
@@ -277,7 +284,7 @@ TEST(SparseSetWithType, IteratorEmptyType) {
     ASSERT_EQ(end - (end - begin), set.begin());
     ASSERT_EQ(end + (begin - end), set.begin());
 
-    ASSERT_EQ(&begin[0], set.begin().operator->());
+    ASSERT_EQ(set.begin().operator->(), nullptr);
 
     ASSERT_LT(begin, end);
     ASSERT_LE(begin, set.begin());
@@ -286,60 +293,12 @@ TEST(SparseSetWithType, IteratorEmptyType) {
     ASSERT_GE(end, set.end());
 
     set.construct(33);
+    auto &&component = *begin;
 
-    ASSERT_EQ(&*begin, &*(begin+1));
-    ASSERT_EQ(&begin[0], &begin[1]);
-    ASSERT_EQ(begin.operator->(), (end-1).operator->());
-}
-
-TEST(SparseSetWithType, ConstIteratorEmptyType) {
-    using iterator_type = typename entt::storage<std::uint64_t, empty_type>::const_iterator_type;
-    entt::storage<std::uint64_t, empty_type> set;
-    set.construct(3);
-
-    iterator_type cend{set.cbegin()};
-    iterator_type cbegin{};
-    cbegin = set.cend();
-    std::swap(cbegin, cend);
-
-    ASSERT_EQ(cbegin, set.cbegin());
-    ASSERT_EQ(cend, set.cend());
-    ASSERT_NE(cbegin, cend);
-
-    ASSERT_EQ(cbegin++, set.cbegin());
-    ASSERT_EQ(cbegin--, set.cend());
-
-    ASSERT_EQ(cbegin+1, set.cend());
-    ASSERT_EQ(cend-1, set.cbegin());
-
-    ASSERT_EQ(++cbegin, set.cend());
-    ASSERT_EQ(--cbegin, set.cbegin());
-
-    ASSERT_EQ(cbegin += 1, set.cend());
-    ASSERT_EQ(cbegin -= 1, set.cbegin());
-
-    ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
-    ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
-
-    ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
-    ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
-
-    ASSERT_EQ(&cbegin[0], set.cbegin().operator->());
-
-    ASSERT_LT(cbegin, cend);
-    ASSERT_LE(cbegin, set.cbegin());
-
-    ASSERT_GT(cend, cbegin);
-    ASSERT_GE(cend, set.cend());
-
-    set.construct(33);
-
-    ASSERT_EQ(&*cbegin, &*(cbegin+1));
-    ASSERT_EQ(&cbegin[0], &cbegin[1]);
-    ASSERT_EQ(cbegin.operator->(), (cend-1).operator->());
+    ASSERT_TRUE((std::is_same_v<decltype(component), empty_type &&>));
 }
 
-TEST(SparseSetWithType, Raw) {
+TEST(Storage, Raw) {
     entt::storage<std::uint64_t, int> set;
 
     set.construct(3, 3);
@@ -355,7 +314,7 @@ TEST(SparseSetWithType, Raw) {
     ASSERT_EQ(*(set.raw() + 2u), 9);
 }
 
-TEST(SparseSetWithType, RawEmptyType) {
+TEST(Storage, RawEmptyType) {
     entt::storage<std::uint64_t, empty_type> set;
 
     set.construct(3);
@@ -364,7 +323,7 @@ TEST(SparseSetWithType, RawEmptyType) {
     ASSERT_EQ(set.try_get(3), set.raw());
 }
 
-TEST(SparseSetWithType, SortOrdered) {
+TEST(Storage, SortOrdered) {
     entt::storage<std::uint64_t, boxed_int> set;
 
     set.construct(12, boxed_int{12});
@@ -400,7 +359,7 @@ TEST(SparseSetWithType, SortOrdered) {
     ASSERT_EQ(begin, end);
 }
 
-TEST(SparseSetWithType, SortReverse) {
+TEST(Storage, SortReverse) {
     entt::storage<std::uint64_t, boxed_int> set;
 
     set.construct(12, boxed_int{1});
@@ -436,7 +395,7 @@ TEST(SparseSetWithType, SortReverse) {
     ASSERT_EQ(begin, end);
 }
 
-TEST(SparseSetWithType, SortUnordered) {
+TEST(Storage, SortUnordered) {
     entt::storage<std::uint64_t, boxed_int> set;
 
     set.construct(12, boxed_int{6});
@@ -472,7 +431,7 @@ TEST(SparseSetWithType, SortUnordered) {
     ASSERT_EQ(begin, end);
 }
 
-TEST(SparseSetWithType, RespectDisjoint) {
+TEST(Storage, RespectDisjoint) {
     entt::storage<std::uint64_t, int> lhs;
     entt::storage<std::uint64_t, int> rhs;
 
@@ -499,7 +458,7 @@ TEST(SparseSetWithType, RespectDisjoint) {
     ASSERT_EQ(begin, end);
 }
 
-TEST(SparseSetWithType, RespectOverlap) {
+TEST(Storage, RespectOverlap) {
     entt::storage<std::uint64_t, int> lhs;
     entt::storage<std::uint64_t, int> rhs;
 
@@ -528,7 +487,7 @@ TEST(SparseSetWithType, RespectOverlap) {
     ASSERT_EQ(begin, end);
 }
 
-TEST(SparseSetWithType, RespectOrdered) {
+TEST(Storage, RespectOrdered) {
     entt::storage<std::uint64_t, int> lhs;
     entt::storage<std::uint64_t, int> rhs;
 
@@ -574,7 +533,7 @@ TEST(SparseSetWithType, RespectOrdered) {
     ASSERT_EQ(*(rhs.data() + 5u), 5u);
 }
 
-TEST(SparseSetWithType, RespectReverse) {
+TEST(Storage, RespectReverse) {
     entt::storage<std::uint64_t, int> lhs;
     entt::storage<std::uint64_t, int> rhs;
 
@@ -620,7 +579,7 @@ TEST(SparseSetWithType, RespectReverse) {
     ASSERT_EQ(*(rhs.data() + 5u), 5u);
 }
 
-TEST(SparseSetWithType, RespectUnordered) {
+TEST(Storage, RespectUnordered) {
     entt::storage<std::uint64_t, int> lhs;
     entt::storage<std::uint64_t, int> rhs;
 
@@ -666,7 +625,7 @@ TEST(SparseSetWithType, RespectUnordered) {
     ASSERT_EQ(*(rhs.data() + 5u), 5u);
 }
 
-TEST(SparseSetWithType, RespectOverlapEmptyType) {
+TEST(Storage, RespectOverlapEmptyType) {
     entt::storage<std::uint64_t, empty_type> lhs;
     entt::storage<std::uint64_t, empty_type> rhs;
 
@@ -687,7 +646,7 @@ TEST(SparseSetWithType, RespectOverlapEmptyType) {
     ASSERT_EQ(std::as_const(lhs).sparse_set<std::uint64_t>::get(42), 1u);
 }
 
-TEST(SparseSetWithType, CanModifyDuringIteration) {
+TEST(Storage, CanModifyDuringIteration) {
     entt::storage<std::uint64_t, int> set;
     set.construct(0, 42);
 
@@ -703,7 +662,7 @@ TEST(SparseSetWithType, CanModifyDuringIteration) {
     (void)entity;
 }
 
-TEST(SparseSetWithType, ReferencesGuaranteed) {
+TEST(Storage, ReferencesGuaranteed) {
     entt::storage<std::uint64_t, boxed_int> set;
 
     set.construct(0, 0);
@@ -731,13 +690,13 @@ TEST(SparseSetWithType, ReferencesGuaranteed) {
     ASSERT_EQ(set.get(1).value, 3);
 }
 
-TEST(SparseSetWithType, MoveOnlyComponent) {
+TEST(Storage, MoveOnlyComponent) {
     // the purpose is to ensure that move only components are always accepted
     entt::storage<std::uint64_t, std::unique_ptr<int>> set;
     (void)set;
 }
 
-TEST(SparseSetWithType, ConstructorExceptionDoesNotAddToSet) {
+TEST(Storage, ConstructorExceptionDoesNotAddToSet) {
     entt::storage<std::uint64_t, throwing_component> set;
 
     try {

+ 2 - 2
test/mod/mod.cpp

@@ -368,7 +368,7 @@ TEST(Mod, Duktape) {
     ASSERT_EQ(registry.view<position>().size(), 3u);
     ASSERT_EQ(registry.view<renderable>().size(), 2u);
 
-    registry.view<duktape_runtime>().each([](auto, const duktape_runtime &runtime) {
+    registry.view<duktape_runtime>().each([](const duktape_runtime &runtime) {
         ASSERT_EQ(runtime.components.size(), 2u);
     });
 
@@ -387,7 +387,7 @@ TEST(Mod, Duktape) {
     ASSERT_EQ(registry.view<position>().size(), 3u);
     ASSERT_EQ(registry.view<renderable>().size(), 2u);
 
-    registry.view<position, renderable, duktape_runtime>().each([](auto, const position &position, const auto &...) {
+    registry.view<position, renderable, duktape_runtime>().each([](const position &position, auto &&...) {
         ASSERT_EQ(position.x, -100.);
         ASSERT_EQ(position.y, -100.);
     });