storage: refine transparent aggregate support

This commit is contained in:
Michele Caini
2023-02-03 18:18:39 +01:00
parent 85b1e57d8d
commit a9883f27c6
2 changed files with 92 additions and 69 deletions

View File

@@ -675,7 +675,7 @@ public:
*/
template<typename... Args>
value_type &emplace(const entity_type entt, Args &&...args) {
if constexpr(std::is_aggregate_v<value_type>) {
if constexpr(sizeof...(Args) != 0u && std ::is_aggregate_v<value_type>) {
const auto it = emplace_element(entt, false, Type{std::forward<Args>(args)...});
return element_at(static_cast<size_type>(it.index()));
} else {

View File

@@ -31,6 +31,15 @@ struct stable_type {
int value;
};
struct aggregate_tracking_type {
~aggregate_tracking_type() {
++counter;
}
static inline int counter;
int value;
};
struct non_default_constructible {
non_default_constructible() = delete;
@@ -87,7 +96,15 @@ inline bool operator==(const boxed_int &lhs, const boxed_int &rhs) {
return lhs.value == rhs.value;
}
TEST(Storage, Functionalities) {
struct Storage: ::testing::Test {
void SetUp() override {
aggregate_tracking_type::counter = 0;
}
};
using StorageDeathTest = Storage;
TEST_F(Storage, Functionalities) {
entt::storage<int> pool;
constexpr auto page_size = decltype(pool)::traits_type::page_size;
@@ -155,7 +172,7 @@ TEST(Storage, Functionalities) {
ASSERT_EQ(pool.capacity(), 0u);
}
TEST(Storage, Move) {
TEST_F(Storage, Move) {
entt::storage<int> pool;
pool.emplace(entt::entity{3}, 3);
@@ -191,7 +208,7 @@ TEST(Storage, Move) {
ASSERT_EQ(other.get(entt::entity{3}), 3);
}
TEST(Storage, Swap) {
TEST_F(Storage, Swap) {
entt::storage<int> pool;
entt::storage<int> other;
@@ -219,7 +236,7 @@ TEST(Storage, Swap) {
ASSERT_EQ(other.get(entt::entity{42}), 41);
}
TEST(Storage, StableSwap) {
TEST_F(Storage, StableSwap) {
entt::storage<stable_type> pool;
entt::storage<stable_type> other;
@@ -247,7 +264,7 @@ TEST(Storage, StableSwap) {
ASSERT_EQ(other.get(entt::entity{42}).value, 41);
}
TEST(Storage, VoidType) {
TEST_F(Storage, VoidType) {
entt::storage<void> pool;
pool.emplace(entt::entity{99});
@@ -265,7 +282,7 @@ TEST(Storage, VoidType) {
ASSERT_FALSE(other.contains(entt::entity{99}));
}
TEST(Storage, EmptyType) {
TEST_F(Storage, EmptyType) {
entt::storage<empty_stable_type> pool;
pool.emplace(entt::entity{99});
@@ -287,7 +304,7 @@ TEST(Storage, EmptyType) {
ASSERT_EQ(pool.get_as_tuple(entt::entity{99}), std::tuple<>{});
}
ENTT_DEBUG_TEST(StorageDeathTest, EmptyType) {
ENTT_DEBUG_TEST_F(StorageDeathTest, EmptyType) {
entt::storage<empty_stable_type> pool;
pool.emplace(entt::entity{99});
@@ -295,7 +312,7 @@ ENTT_DEBUG_TEST(StorageDeathTest, EmptyType) {
ASSERT_DEATH([[maybe_unused]] auto tup = pool.get_as_tuple(entt::entity{3}), "");
}
TEST(Storage, Insert) {
TEST_F(Storage, Insert) {
entt::storage<stable_type> pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
pool.insert(std::begin(entities), std::end(entities), stable_type{99});
@@ -321,7 +338,7 @@ TEST(Storage, Insert) {
ASSERT_EQ(pool.get(entities[1u]).value, 42);
}
TEST(Storage, InsertEmptyType) {
TEST_F(Storage, InsertEmptyType) {
entt::storage<empty_stable_type> pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
@@ -344,7 +361,7 @@ TEST(Storage, InsertEmptyType) {
ASSERT_EQ(pool.index(entities[1u]), 2u);
}
TEST(Storage, Erase) {
TEST_F(Storage, Erase) {
entt::storage<int> pool;
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}};
@@ -377,7 +394,7 @@ TEST(Storage, Erase) {
ASSERT_EQ(*pool.begin(), 1);
}
TEST(Storage, CrossErase) {
TEST_F(Storage, CrossErase) {
entt::sparse_set set;
entt::storage<int> pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
@@ -392,7 +409,7 @@ TEST(Storage, CrossErase) {
ASSERT_EQ(pool.raw()[0u][0u], 3);
}
TEST(Storage, StableErase) {
TEST_F(Storage, StableErase) {
entt::storage<stable_type> pool;
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}};
@@ -486,7 +503,7 @@ TEST(Storage, StableErase) {
ASSERT_EQ(pool.get(entities[2u]).value, 1);
}
TEST(Storage, CrossStableErase) {
TEST_F(Storage, CrossStableErase) {
entt::sparse_set set;
entt::storage<stable_type> pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
@@ -501,7 +518,7 @@ TEST(Storage, CrossStableErase) {
ASSERT_EQ(pool.raw()[0u][0u].value, 3);
}
TEST(Storage, Remove) {
TEST_F(Storage, Remove) {
entt::storage<int> pool;
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}};
@@ -535,7 +552,7 @@ TEST(Storage, Remove) {
ASSERT_EQ(*pool.begin(), 1);
}
TEST(Storage, CrossRemove) {
TEST_F(Storage, CrossRemove) {
entt::sparse_set set;
entt::storage<int> pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
@@ -550,7 +567,7 @@ TEST(Storage, CrossRemove) {
ASSERT_EQ(pool.raw()[0u][0u], 3);
}
TEST(Storage, StableRemove) {
TEST_F(Storage, StableRemove) {
entt::storage<stable_type> pool;
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}};
@@ -647,7 +664,7 @@ TEST(Storage, StableRemove) {
ASSERT_EQ(pool.get(entities[2u]).value, 1);
}
TEST(Storage, CrossStableRemove) {
TEST_F(Storage, CrossStableRemove) {
entt::sparse_set set;
entt::storage<stable_type> pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
@@ -662,7 +679,7 @@ TEST(Storage, CrossStableRemove) {
ASSERT_EQ(pool.raw()[0u][0u].value, 3);
}
TEST(Storage, TypeFromBase) {
TEST_F(Storage, TypeFromBase) {
entt::storage<int> pool;
entt::sparse_set &base = pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
@@ -696,7 +713,7 @@ TEST(Storage, TypeFromBase) {
ASSERT_TRUE(pool.empty());
}
TEST(Storage, EmptyTypeFromBase) {
TEST_F(Storage, EmptyTypeFromBase) {
entt::storage<empty_stable_type> pool;
entt::sparse_set &base = pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
@@ -747,7 +764,7 @@ TEST(Storage, EmptyTypeFromBase) {
}
}
TEST(Storage, NonDefaultConstructibleTypeFromBase) {
TEST_F(Storage, NonDefaultConstructibleTypeFromBase) {
entt::storage<non_default_constructible> pool;
entt::sparse_set &base = pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
@@ -786,7 +803,7 @@ TEST(Storage, NonDefaultConstructibleTypeFromBase) {
ASSERT_TRUE(pool.empty());
}
TEST(Storage, NonCopyConstructibleTypeFromBase) {
TEST_F(Storage, NonCopyConstructibleTypeFromBase) {
entt::storage<std::unique_ptr<int>> pool;
entt::sparse_set &base = pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
@@ -825,7 +842,7 @@ TEST(Storage, NonCopyConstructibleTypeFromBase) {
ASSERT_FALSE(pool.empty());
}
TEST(Storage, Compact) {
TEST_F(Storage, Compact) {
entt::storage<stable_type> pool;
ASSERT_TRUE(pool.empty());
@@ -871,7 +888,7 @@ TEST(Storage, Compact) {
ASSERT_TRUE(pool.empty());
}
TEST(Storage, ShrinkToFit) {
TEST_F(Storage, ShrinkToFit) {
entt::storage<int> pool;
constexpr auto page_size = decltype(pool)::traits_type::page_size;
@@ -901,16 +918,22 @@ TEST(Storage, ShrinkToFit) {
ASSERT_EQ(pool.size(), 0u);
}
TEST(Storage, AggregatesMustWork) {
struct aggregate_type {
int value;
};
TEST_F(Storage, AggregatesMustWork) {
static_assert(std::is_aggregate_v<aggregate_tracking_type>);
entt::storage<aggregate_tracking_type> storage{};
// the goal of this test is to enforce the requirements for aggregate types
entt::storage<aggregate_type>{}.emplace(entt::entity{0}, 42);
// aggregate types with no args enter the non-aggregate path
storage.emplace(entt::entity{0});
ASSERT_EQ(aggregate_tracking_type::counter, 0);
// aggregate types with args work despite the lack of support in the standard library
storage.emplace(entt::entity{1}, 42);
ASSERT_EQ(aggregate_tracking_type::counter, 1);
}
TEST(Storage, SelfMoveSupport) {
TEST_F(Storage, SelfMoveSupport) {
// see #37 - this test shouldn't crash, that's all
entt::storage<std::unordered_set<int>> pool;
entt::entity entity{};
@@ -923,7 +946,7 @@ TEST(Storage, SelfMoveSupport) {
ASSERT_FALSE(pool.contains(entity));
}
TEST(Storage, SelfMoveSupportInPlaceDelete) {
TEST_F(Storage, SelfMoveSupportInPlaceDelete) {
// see #37 - this test shouldn't crash, that's all
entt::storage<std::unordered_set<char>> pool;
entt::entity entity{};
@@ -936,7 +959,7 @@ TEST(Storage, SelfMoveSupportInPlaceDelete) {
ASSERT_FALSE(pool.contains(entity));
}
TEST(Storage, Iterator) {
TEST_F(Storage, Iterator) {
using iterator = typename entt::storage<boxed_int>::iterator;
static_assert(std::is_same_v<iterator::value_type, boxed_int>);
@@ -997,7 +1020,7 @@ TEST(Storage, Iterator) {
ASSERT_EQ(begin[1u], boxed_int{42});
}
TEST(Storage, ConstIterator) {
TEST_F(Storage, ConstIterator) {
using iterator = typename entt::storage<boxed_int>::const_iterator;
static_assert(std::is_same_v<iterator::value_type, boxed_int>);
@@ -1060,7 +1083,7 @@ TEST(Storage, ConstIterator) {
ASSERT_EQ(cbegin[1u], boxed_int{42});
}
TEST(Storage, ReverseIterator) {
TEST_F(Storage, ReverseIterator) {
using reverse_iterator = typename entt::storage<boxed_int>::reverse_iterator;
static_assert(std::is_same_v<reverse_iterator::value_type, boxed_int>);
@@ -1121,7 +1144,7 @@ TEST(Storage, ReverseIterator) {
ASSERT_EQ(begin[1u], boxed_int{3});
}
TEST(Storage, ConstReverseIterator) {
TEST_F(Storage, ConstReverseIterator) {
using const_reverse_iterator = typename entt::storage<boxed_int>::const_reverse_iterator;
static_assert(std::is_same_v<const_reverse_iterator::value_type, boxed_int>);
@@ -1184,7 +1207,7 @@ TEST(Storage, ConstReverseIterator) {
ASSERT_EQ(cbegin[1u], boxed_int{3});
}
TEST(Storage, IteratorConversion) {
TEST_F(Storage, IteratorConversion) {
entt::storage<boxed_int> pool;
pool.emplace(entt::entity{3}, 42);
@@ -1207,7 +1230,7 @@ TEST(Storage, IteratorConversion) {
ASSERT_NE(++cit, it);
}
TEST(Storage, IteratorPageSizeAwareness) {
TEST_F(Storage, IteratorPageSizeAwareness) {
entt::storage<std::unordered_set<char>> pool;
constexpr auto page_size = decltype(pool)::traits_type::page_size;
const std::unordered_set<char> check{'c'};
@@ -1222,7 +1245,7 @@ TEST(Storage, IteratorPageSizeAwareness) {
ASSERT_EQ(*pool.begin(), check);
}
TEST(Storage, Iterable) {
TEST_F(Storage, Iterable) {
using iterator = typename entt::storage<boxed_int>::iterable::iterator;
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity, boxed_int &>>);
@@ -1266,7 +1289,7 @@ TEST(Storage, Iterable) {
}
}
TEST(Storage, ConstIterable) {
TEST_F(Storage, ConstIterable) {
using iterator = typename entt::storage<boxed_int>::const_iterable::iterator;
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity, const boxed_int &>>);
@@ -1310,7 +1333,7 @@ TEST(Storage, ConstIterable) {
}
}
TEST(Storage, IterableIteratorConversion) {
TEST_F(Storage, IterableIteratorConversion) {
entt::storage<boxed_int> pool;
pool.emplace(entt::entity{3}, 42);
@@ -1324,7 +1347,7 @@ TEST(Storage, IterableIteratorConversion) {
ASSERT_NE(++cit, it);
}
TEST(Storage, EmptyTypeIterable) {
TEST_F(Storage, EmptyTypeIterable) {
using iterator = typename entt::storage<empty_stable_type>::iterable::iterator;
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
@@ -1365,7 +1388,7 @@ TEST(Storage, EmptyTypeIterable) {
}
}
TEST(Storage, IterableAlgorithmCompatibility) {
TEST_F(Storage, IterableAlgorithmCompatibility) {
entt::storage<boxed_int> pool;
pool.emplace(entt::entity{3}, 42);
@@ -1375,7 +1398,7 @@ TEST(Storage, IterableAlgorithmCompatibility) {
ASSERT_EQ(std::get<0>(*it), entt::entity{3});
}
TEST(Storage, Raw) {
TEST_F(Storage, Raw) {
entt::storage<int> pool;
pool.emplace(entt::entity{3}, 3);
@@ -1391,7 +1414,7 @@ TEST(Storage, Raw) {
ASSERT_EQ(pool.raw()[0u][2u], 9);
}
TEST(Storage, SwapElements) {
TEST_F(Storage, SwapElements) {
entt::storage<int> pool;
pool.emplace(entt::entity{3}, 3);
@@ -1413,7 +1436,7 @@ TEST(Storage, SwapElements) {
ASSERT_EQ(pool.index(entt::entity{42}), 0u);
}
TEST(Storage, StableSwapElements) {
TEST_F(Storage, StableSwapElements) {
entt::storage<stable_type> pool;
pool.emplace(entt::entity{3}, 3);
@@ -1435,7 +1458,7 @@ TEST(Storage, StableSwapElements) {
ASSERT_EQ(pool.index(entt::entity{42}), 0u);
}
TEST(Storage, SortOrdered) {
TEST_F(Storage, SortOrdered) {
entt::storage<boxed_int> pool;
entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
boxed_int values[5u]{{12}, {9}, {6}, {3}, {1}};
@@ -1447,7 +1470,7 @@ TEST(Storage, SortOrdered) {
ASSERT_TRUE(std::equal(std::rbegin(values), std::rend(values), pool.begin(), pool.end()));
}
TEST(Storage, SortReverse) {
TEST_F(Storage, SortReverse) {
entt::storage<boxed_int> pool;
entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
boxed_int values[5u]{{1}, {3}, {6}, {9}, {12}};
@@ -1459,7 +1482,7 @@ TEST(Storage, SortReverse) {
ASSERT_TRUE(std::equal(std::begin(values), std::end(values), pool.begin(), pool.end()));
}
TEST(Storage, SortUnordered) {
TEST_F(Storage, SortUnordered) {
entt::storage<boxed_int> pool;
entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
boxed_int values[5u]{{6}, {3}, {1}, {9}, {12}};
@@ -1484,7 +1507,7 @@ TEST(Storage, SortUnordered) {
ASSERT_EQ(pool.data()[4u], entities[2u]);
}
TEST(Storage, SortRange) {
TEST_F(Storage, SortRange) {
entt::storage<boxed_int> pool;
entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
boxed_int values[5u]{{3}, {6}, {1}, {9}, {12}};
@@ -1524,7 +1547,7 @@ TEST(Storage, SortRange) {
ASSERT_EQ(pool.data()[4u], entities[2u]);
}
TEST(Storage, RespectDisjoint) {
TEST_F(Storage, RespectDisjoint) {
entt::storage<int> lhs;
entt::storage<int> rhs;
@@ -1541,7 +1564,7 @@ TEST(Storage, RespectDisjoint) {
ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end()));
}
TEST(Storage, RespectOverlap) {
TEST_F(Storage, RespectOverlap) {
entt::storage<int> lhs;
entt::storage<int> rhs;
@@ -1574,7 +1597,7 @@ TEST(Storage, RespectOverlap) {
ASSERT_EQ(lhs.data()[2u], lhs_entities[1u]);
}
TEST(Storage, RespectOrdered) {
TEST_F(Storage, RespectOrdered) {
entt::storage<int> lhs;
entt::storage<int> rhs;
@@ -1598,7 +1621,7 @@ TEST(Storage, RespectOrdered) {
ASSERT_TRUE(std::equal(std::rbegin(rhs_values), std::rend(rhs_values), rhs.begin(), rhs.end()));
}
TEST(Storage, RespectReverse) {
TEST_F(Storage, RespectReverse) {
entt::storage<int> lhs;
entt::storage<int> rhs;
@@ -1637,7 +1660,7 @@ TEST(Storage, RespectReverse) {
ASSERT_EQ(rhs.data()[5u], rhs_entities[0u]);
}
TEST(Storage, RespectUnordered) {
TEST_F(Storage, RespectUnordered) {
entt::storage<int> lhs;
entt::storage<int> rhs;
@@ -1676,7 +1699,7 @@ TEST(Storage, RespectUnordered) {
ASSERT_EQ(rhs.data()[5u], rhs_entities[5u]);
}
TEST(Storage, CanModifyDuringIteration) {
TEST_F(Storage, CanModifyDuringIteration) {
entt::storage<int> pool;
auto *ptr = &pool.emplace(entt::entity{0}, 42);
constexpr auto page_size = decltype(pool)::traits_type::page_size;
@@ -1693,7 +1716,7 @@ TEST(Storage, CanModifyDuringIteration) {
[[maybe_unused]] const int &value = *it;
}
TEST(Storage, ReferencesGuaranteed) {
TEST_F(Storage, ReferencesGuaranteed) {
entt::storage<boxed_int> pool;
pool.emplace(entt::entity{0}, 0);
@@ -1721,17 +1744,17 @@ TEST(Storage, ReferencesGuaranteed) {
ASSERT_EQ(pool.get(entt::entity{1}).value, 3);
}
TEST(Storage, MoveOnlyComponent) {
TEST_F(Storage, MoveOnlyComponent) {
// the purpose is to ensure that move only components are always accepted
[[maybe_unused]] entt::storage<std::unique_ptr<int>> pool;
}
TEST(Storage, PinnedComponent) {
TEST_F(Storage, PinnedComponent) {
// the purpose is to ensure that non-movable components are always accepted
[[maybe_unused]] entt::storage<pinned_type> pool;
}
ENTT_DEBUG_TEST(StorageDeathTest, PinnedComponent) {
ENTT_DEBUG_TEST_F(StorageDeathTest, PinnedComponent) {
entt::storage<pinned_type> pool;
const entt::entity entity{0};
const entt::entity destroy{1};
@@ -1748,7 +1771,7 @@ ENTT_DEBUG_TEST(StorageDeathTest, PinnedComponent) {
ASSERT_DEATH(pool.sort([](auto &&lhs, auto &&rhs) { return lhs < rhs; }), "");
}
TEST(Storage, UpdateFromDestructor) {
TEST_F(Storage, UpdateFromDestructor) {
auto test = [](const auto target) {
constexpr auto size = 10u;
@@ -1779,7 +1802,7 @@ TEST(Storage, UpdateFromDestructor) {
test(entt::entity{0u});
}
TEST(Storage, CreateFromConstructor) {
TEST_F(Storage, CreateFromConstructor) {
entt::storage<create_from_constructor> pool;
const entt::entity entity{0u};
const entt::entity other{1u};
@@ -1790,7 +1813,7 @@ TEST(Storage, CreateFromConstructor) {
ASSERT_EQ(pool.get(other).child, static_cast<entt::entity>(entt::null));
}
TEST(Storage, CustomAllocator) {
TEST_F(Storage, CustomAllocator) {
auto test = [](auto pool, auto alloc) {
pool.reserve(1u);
@@ -1837,7 +1860,7 @@ TEST(Storage, CustomAllocator) {
test(entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>{allocator}, allocator);
}
TEST(Storage, ThrowingAllocator) {
TEST_F(Storage, ThrowingAllocator) {
auto test = [](auto pool) {
using pool_allocator_type = typename decltype(pool)::allocator_type;
using value_type = typename decltype(pool)::value_type;
@@ -1901,7 +1924,7 @@ TEST(Storage, ThrowingAllocator) {
test(entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>{});
}
TEST(Storage, ThrowingComponent) {
TEST_F(Storage, ThrowingComponent) {
entt::storage<test::throwing_type> pool;
test::throwing_type::trigger_on_value = 42;
@@ -1955,7 +1978,7 @@ TEST(Storage, ThrowingComponent) {
#if defined(ENTT_HAS_TRACKED_MEMORY_RESOURCE)
TEST(Storage, NoUsesAllocatorConstruction) {
TEST_F(Storage, NoUsesAllocatorConstruction) {
test::tracked_memory_resource memory_resource{};
entt::basic_storage<int, entt::entity, std::pmr::polymorphic_allocator<int>> pool{&memory_resource};
const entt::entity entity{};
@@ -1970,7 +1993,7 @@ TEST(Storage, NoUsesAllocatorConstruction) {
ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u);
}
TEST(Storage, UsesAllocatorConstruction) {
TEST_F(Storage, UsesAllocatorConstruction) {
using string_type = typename test::tracked_memory_resource::string_type;
test::tracked_memory_resource memory_resource{};
@@ -1989,13 +2012,13 @@ TEST(Storage, UsesAllocatorConstruction) {
#endif
TEST(Storage, StorageType) {
TEST_F(Storage, StorageType) {
// just a bunch of static asserts to avoid regressions
static_assert(std::is_same_v<entt::storage_type_t<char, entt::entity>, entt::sigh_mixin<entt::basic_storage<char, entt::entity>>>);
static_assert(std::is_same_v<entt::storage_type_t<int>, entt::sigh_mixin<entt::storage<int>>>);
}
TEST(Storage, StorageFor) {
TEST_F(Storage, StorageFor) {
// just a bunch of static asserts to avoid regressions
static_assert(std::is_same_v<entt::storage_for_t<const double, entt::entity>, const entt::sigh_mixin<entt::basic_storage<double, entt::entity>>>);
static_assert(std::is_same_v<entt::storage_for_t<char, entt::entity>, entt::sigh_mixin<entt::basic_storage<char, entt::entity>>>);