diff --git a/src/entt/entity/fwd.hpp b/src/entt/entity/fwd.hpp index 8aba8e1ba..a1e1cff43 100644 --- a/src/entt/entity/fwd.hpp +++ b/src/entt/entity/fwd.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "../core/fwd.hpp" #include "../core/type_traits.hpp" @@ -28,7 +29,7 @@ class basic_sparse_set; template, typename = void> class basic_storage; -template> +template class basic_table; template @@ -79,7 +80,7 @@ using storage = basic_storage; * @tparam Type Element types. */ template -using table = basic_table>; +using table = basic_table...>; /** * @brief Alias declaration for the most common use case. diff --git a/src/entt/entity/table.hpp b/src/entt/entity/table.hpp index b0bcb3f6f..7e0be733c 100644 --- a/src/entt/entity/table.hpp +++ b/src/entt/entity/table.hpp @@ -3,13 +3,10 @@ #include #include -#include #include #include #include -#include #include "../config/config.h" -#include "../core/compressed_pair.hpp" #include "../core/iterator.hpp" #include "fwd.hpp" @@ -146,44 +143,46 @@ template * no guarantees that objects are returned in the insertion order when iterate * a table. Do not make assumption on the order in any case. * - * @tparam Row Element types. - * @tparam Allocator Type of allocator used to manage memory and elements. + * @tparam Container Sequence container row types. */ -template -class basic_table, Allocator> { - using alloc_traits = std::allocator_traits; - static_assert(sizeof...(Row) != 0u, "Empty tables not allowed"); - - template - using container_for = std::vector>; - - using container_type = std::tuple...>; +template +class basic_table { + using container_type = std::tuple; public: - /*! @brief Allocator type. */ - using allocator_type = Allocator; /*! @brief Unsigned integer type. */ using size_type = std::size_t; /*! @brief Input iterator type. */ - using iterator = internal::table_iterator::iterator...>; + using iterator = internal::table_iterator; /*! @brief Constant input iterator type. */ - using const_iterator = internal::table_iterator::const_iterator...>; + using const_iterator = internal::table_iterator; /*! @brief Reverse iterator type. */ - using reverse_iterator = internal::table_iterator::reverse_iterator...>; + using reverse_iterator = internal::table_iterator; /*! @brief Constant reverse iterator type. */ - using const_reverse_iterator = internal::table_iterator::const_reverse_iterator...>; + using const_reverse_iterator = internal::table_iterator; /*! @brief Default constructor. */ basic_table() - : basic_table{allocator_type{}} { + : payload{} { } /** - * @brief Constructs an empty table with a given allocator. - * @param allocator The allocator to use. + * @brief Copy constructs the underlying containers. + * @param container The containers to copy from. */ - explicit basic_table(const allocator_type &allocator) - : payload{container_type{container_for{allocator}...}, allocator} {} + explicit basic_table(const Container &...container) noexcept + : payload{container...} { + ENTT_ASSERT((((std::get(payload).size() * sizeof...(Container)) == (std::get(payload).size() + ...)) && ...), "Unexpected container size"); + } + + /** + * @brief Move constructs the underlying containers. + * @param container The containers to move from. + */ + explicit basic_table(Container &&...container) noexcept + : payload{std::move(container)...} { + ENTT_ASSERT((((std::get(payload).size() * sizeof...(Container)) == (std::get(payload).size() + ...)) && ...), "Unexpected container size"); + } /** * @brief Move constructor. @@ -192,15 +191,48 @@ public: basic_table(basic_table &&other) noexcept : payload{std::move(other.payload)} {} + /** + * @brief Constructs the underlying containers using a given allocator. + * @tparam Allocator Type of allocator. + * @param allocator A valid allocator. + */ + template + explicit basic_table(const Allocator &allocator) + : payload{Container{allocator}...} {} + + /** + * @brief Copy constructs the underlying containers using a given allocator. + * @tparam Allocator Type of allocator. + * @param container The containers to copy from. + * @param allocator A valid allocator. + */ + template + basic_table(const Container &...container, const Allocator &allocator) noexcept + : payload{Container{container, allocator}...} { + ENTT_ASSERT((((std::get(payload).size() * sizeof...(Container)) == (std::get(payload).size() + ...)) && ...), "Unexpected container size"); + } + + /** + * @brief Move constructs the underlying containers using a given allocator. + * @tparam Allocator Type of allocator. + * @param container The containers to move from. + * @param allocator A valid allocator. + */ + template + basic_table(Container &&...container, const Allocator &allocator) noexcept + : payload{Container{std::move(container), allocator}...} { + ENTT_ASSERT((((std::get(payload).size() * sizeof...(Container)) == (std::get(payload).size() + ...)) && ...), "Unexpected container size"); + } + /** * @brief Allocator-extended move constructor. + * @tparam Allocator Type of allocator. * @param other The instance to move from. * @param allocator The allocator to use. */ - basic_table(basic_table &&other, const allocator_type &allocator) noexcept - : payload{container_type{container_for{std::move(std::get>(other.payload.first())), allocator}...}, allocator} { - ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a table is not allowed"); - } + template + basic_table(basic_table &&other, const Allocator &allocator) noexcept + : payload{Container{std::move(std::get(other.payload)), allocator}...} {} /** * @brief Move assignment operator. @@ -208,7 +240,6 @@ public: * @return This table. */ basic_table &operator=(basic_table &&other) noexcept { - ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a table is not allowed"); payload = std::move(other.payload); return *this; } @@ -222,14 +253,6 @@ public: swap(payload, other.payload); } - /** - * @brief Returns the associated allocator. - * @return The associated allocator. - */ - [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { - return payload.second(); - } - /** * @brief Increases the capacity of a table. * @@ -239,7 +262,7 @@ public: * @param cap Desired capacity. */ void reserve(const size_type cap) { - (std::get>(payload.first()).reserve(cap), ...); + (std::get(payload).reserve(cap), ...); } /** @@ -248,12 +271,12 @@ public: * @return Capacity of the table. */ [[nodiscard]] size_type capacity() const noexcept { - return std::get<0>(payload.first()).capacity(); + return std::get<0>(payload).capacity(); } /*! @brief Requests the removal of unused capacity. */ void shrink_to_fit() { - (std::get>(payload.first()).shrink_to_fit(), ...); + (std::get(payload).shrink_to_fit(), ...); } /** @@ -261,7 +284,7 @@ public: * @return Number of rows. */ [[nodiscard]] size_type size() const noexcept { - return std::get<0>(payload.first()).size(); + return std::get<0>(payload).size(); } /** @@ -269,7 +292,7 @@ public: * @return True if the table is empty, false otherwise. */ [[nodiscard]] bool empty() const noexcept { - return std::get<0>(payload.first()).empty(); + return std::get<0>(payload).empty(); } /** @@ -280,7 +303,7 @@ public: * @return An iterator to the first row of the table. */ [[nodiscard]] const_iterator cbegin() const noexcept { - return {std::get>(payload.first()).cbegin()...}; + return {std::get(payload).cbegin()...}; } /*! @copydoc cbegin */ @@ -290,7 +313,7 @@ public: /*! @copydoc begin */ [[nodiscard]] iterator begin() noexcept { - return {std::get>(payload.first()).begin()...}; + return {std::get(payload).begin()...}; } /** @@ -298,7 +321,7 @@ public: * @return An iterator to the element following the last row of the table. */ [[nodiscard]] const_iterator cend() const noexcept { - return {std::get>(payload.first()).cend()...}; + return {std::get(payload).cend()...}; } /*! @copydoc cend */ @@ -308,7 +331,7 @@ public: /*! @copydoc end */ [[nodiscard]] iterator end() noexcept { - return {std::get>(payload.first()).end()...}; + return {std::get(payload).end()...}; } /** @@ -319,7 +342,7 @@ public: * @return An iterator to the first row of the reversed table. */ [[nodiscard]] const_reverse_iterator crbegin() const noexcept { - return {std::get>(payload.first()).crbegin()...}; + return {std::get(payload).crbegin()...}; } /*! @copydoc crbegin */ @@ -329,7 +352,7 @@ public: /*! @copydoc rbegin */ [[nodiscard]] reverse_iterator rbegin() noexcept { - return {std::get>(payload.first()).rbegin()...}; + return {std::get(payload).rbegin()...}; } /** @@ -338,7 +361,7 @@ public: * table. */ [[nodiscard]] const_reverse_iterator crend() const noexcept { - return {std::get>(payload.first()).crend()...}; + return {std::get(payload).crend()...}; } /*! @copydoc crend */ @@ -348,7 +371,7 @@ public: /*! @copydoc rend */ [[nodiscard]] reverse_iterator rend() noexcept { - return {std::get>(payload.first()).rend()...}; + return {std::get(payload).rend()...}; } /** @@ -358,11 +381,11 @@ public: * @return A reference to the newly created row data. */ template - std::tuple emplace(Args &&...args) { + std::tuple emplace(Args &&...args) { if constexpr(sizeof...(Args) == 0u) { - return std::forward_as_tuple(std::get>(payload.first()).emplace_back()...); + return std::forward_as_tuple(std::get(payload).emplace_back()...); } else { - return std::forward_as_tuple(std::get>(payload.first()).emplace_back(std::forward(args))...); + return std::forward_as_tuple(std::get(payload).emplace_back(std::forward(args))...); } } @@ -373,7 +396,7 @@ public: */ iterator erase(const_iterator pos) { const auto diff = pos - begin(); - return {std::get>(payload.first()).erase(std::get>(payload.first()).begin() + diff)...}; + return {std::get(payload).erase(std::get(payload).begin() + diff)...}; } /** @@ -390,26 +413,36 @@ public: * @param pos The row for which to return the data. * @return The row data at specified location. */ - [[nodiscard]] std::tuple operator[](const size_type pos) const { + [[nodiscard]] std::tuple operator[](const size_type pos) const { ENTT_ASSERT(pos < size(), "Index out of bounds"); - return std::forward_as_tuple(std::get>(payload.first())[pos]...); + return std::forward_as_tuple(std::get(payload)[pos]...); } /*! @copydoc operator[] */ - [[nodiscard]] std::tuple operator[](const size_type pos) { + [[nodiscard]] std::tuple operator[](const size_type pos) { ENTT_ASSERT(pos < size(), "Index out of bounds"); - return std::forward_as_tuple(std::get>(payload.first())[pos]...); + return std::forward_as_tuple(std::get(payload)[pos]...); } /*! @brief Clears a table. */ void clear() { - (std::get>(payload.first()).clear(), ...); + (std::get(payload).clear(), ...); } private: - compressed_pair payload; + container_type payload; }; } // namespace entt +/*! @cond TURN_OFF_DOXYGEN */ +namespace std { + +template +struct uses_allocator, Allocator> + : std::bool_constant<(std::uses_allocator_v && ...)> {}; + +} // namespace std +/*! @endcond */ + #endif diff --git a/test/entt/entity/table.cpp b/test/entt/entity/table.cpp index ed8612964..b4d06ff58 100644 --- a/test/entt/entity/table.cpp +++ b/test/entt/entity/table.cpp @@ -10,13 +10,43 @@ #include "../../common/throwing_allocator.hpp" TEST(Table, Constructors) { - entt::table table; + const std::vector vec_of_int{1}; + const std::vector vec_of_char{'a'}; + entt::table table{}; - ASSERT_NO_THROW([[maybe_unused]] auto alloc = table.get_allocator()); + ASSERT_TRUE(table.empty()); table = entt::table{std::allocator{}}; - ASSERT_NO_THROW([[maybe_unused]] auto alloc = table.get_allocator()); + ASSERT_TRUE(table.empty()); + + table = entt::table{vec_of_int, vec_of_char}; + + ASSERT_EQ(table.size(), 1); + + table = entt::table{std::vector{1, 2}, std::vector{'a', 'b'}}; + + ASSERT_EQ(table.size(), 2); + + table = entt::table{vec_of_int, vec_of_char, std::allocator{}}; + + ASSERT_EQ(table.size(), 1); + + table = entt::table{std::vector{1, 2}, std::vector{'a', 'b'}, std::allocator{}}; + + ASSERT_EQ(table.size(), 2); +} + +ENTT_DEBUG_TEST(TableDeathTest, Constructors) { + const std::vector vec_of_int{0}; + const std::vector vec_of_char{}; + entt::table table{}; + + ASSERT_DEATH((table = entt::table{vec_of_int, vec_of_char}), ""); + ASSERT_DEATH((table = entt::table{std::vector{}, std::vector{'\0'}}), ""); + + ASSERT_DEATH((table = entt::table{vec_of_int, vec_of_char, std::allocator{}}), ""); + ASSERT_DEATH((table = entt::table{std::vector{}, std::vector{'\0'}, std::allocator{}}), ""); } TEST(Table, Move) { @@ -399,7 +429,7 @@ TEST(Table, Erase) { ASSERT_EQ(table.size(), 0u); } -TEST(TableDeathTest, Erase) { +ENTT_DEBUG_TEST(TableDeathTest, Erase) { entt::table table; ASSERT_DEATH(table.erase(0u), ""); @@ -451,7 +481,7 @@ TEST(Table, Clear) { TEST(Table, CustomAllocator) { const test::throwing_allocator allocator{}; - entt::basic_table, test::throwing_allocator> table{allocator}; + entt::basic_table>, std::vector>> table{allocator}; table.reserve(1u); @@ -493,14 +523,15 @@ TEST(Table, CustomAllocator) { } TEST(Table, ThrowingAllocator) { - entt::basic_table, test::throwing_allocator> table{}; + test::throwing_allocator allocator{}; + entt::basic_table>, std::vector>> table{allocator}; - table.get_allocator().template throw_counter(0u); + allocator.throw_counter(0u); ASSERT_THROW(table.reserve(1u), test::throwing_allocator_exception); - table.get_allocator().template throw_counter(0u); - table.get_allocator().template throw_counter(0u); + allocator.throw_counter(0u); + allocator.throw_counter(0u); ASSERT_THROW(table.emplace(), test::throwing_allocator_exception); ASSERT_THROW(table.emplace(3, 'c'), test::throwing_allocator_exception);