From cdaced4df6b1b37e9ac57f99d9981ed8a68db0e5 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Wed, 23 Feb 2022 00:19:55 +0100 Subject: [PATCH] memory: uses_allocator_construction_args pairs support --- TODO | 2 +- src/entt/core/memory.hpp | 85 +++++++++++++++++++++++++++++++-------- test/entt/core/memory.cpp | 46 +++++++++++++++++++++ 3 files changed, 115 insertions(+), 18 deletions(-) diff --git a/TODO b/TODO index 87ac1b3c2..1c9ea43e6 100644 --- a/TODO +++ b/TODO @@ -4,7 +4,7 @@ * add examples (and credits) from @alanjfs :) WIP: -* uses-allocator construction: dense map, compressed pair, any (with allocator support), cache, dispatcher, poly, ... +* uses-allocator construction: dense map, compressed pair, any (with allocator support), cache, dispatcher, poly, storage/map/allocate_unique (with uninitialized_construct_using_allocator/construct_at), ... * add an ENTT_NOEXCEPT with args and use it to make ie compressed_pair conditionally noexcept * process scheduler: reviews, use free lists internally * runtime events (emitter) diff --git a/src/entt/core/memory.hpp b/src/entt/core/memory.hpp index f3a3cad07..b52b204dd 100644 --- a/src/entt/core/memory.hpp +++ b/src/entt/core/memory.hpp @@ -163,16 +163,78 @@ auto allocate_unique(Allocator &allocator, Args &&...args) { return std::unique_ptr>{ptr, type_allocator}; } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct uses_allocator_construction { + template + static constexpr auto args(const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT { + if constexpr(!std::uses_allocator_v && std::is_constructible_v) { + return std::forward_as_tuple(std::forward(params)...); + } else { + static_assert(std::uses_allocator_v, "Ill-formed request"); + + if constexpr(std::is_constructible_v) { + return std::tuple(std::allocator_arg, allocator, std::forward(params)...); + } else { + static_assert(std::is_constructible_v, "Ill-formed request"); + return std::forward_as_tuple(std::forward(params)..., allocator); + } + } + } +}; + +template +struct uses_allocator_construction> { + using type = std::pair; + + template + static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT { + return std::make_tuple( + std::piecewise_construct, + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(first)), + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); + } + + template + static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward(first)), std::forward_as_tuple(std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator, const std::pair &value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); + } + + template + static constexpr auto args(const Allocator &allocator, std::pair &&value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + /** * @brief Uses-allocator construction utility (waiting for C++20). * * Primarily intended for internal use. Prepares the argument list needed to * create an object of a given type by means of uses-allocator construction. * - * @warning - * Unlike the standard implementation, this utility does not differentiate - * between pair and non-pair types. - * * @tparam Type Type to return arguments for. * @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Args Types of arguments to use to construct the object. @@ -182,18 +244,7 @@ auto allocate_unique(Allocator &allocator, Args &&...args) { */ template constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT { - if constexpr(!std::uses_allocator_v && std::is_constructible_v) { - return std::forward_as_tuple(std::forward(args)...); - } else { - static_assert(std::uses_allocator_v, "Ill-formed request"); - - if constexpr(std::is_constructible_v) { - return std::tuple(std::allocator_arg, allocator, std::forward(args)...); - } else { - static_assert(std::is_constructible_v, "Ill-formed request"); - return std::forward_as_tuple(std::forward(args)..., allocator); - } - } + return internal::uses_allocator_construction::args(allocator, std::forward(args)...); } /** @@ -211,7 +262,7 @@ constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args */ template constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) { - return std::make_from_tuple(entt::uses_allocator_construction_args(allocator, std::forward(args)...)); + return std::make_from_tuple(internal::uses_allocator_construction::args(allocator, std::forward(args)...)); } } // namespace entt diff --git a/test/entt/core/memory.cpp b/test/entt/core/memory.cpp index ec482b659..ce39a7625 100644 --- a/test/entt/core/memory.cpp +++ b/test/entt/core/memory.cpp @@ -148,6 +148,52 @@ TEST(UsesAllocatorConstructionArgs, TrailingAllocatorConvention) { ASSERT_EQ(std::get<0>(args), size); } +TEST(UsesAllocatorConstructionArgs, PairPiecewiseConstruct) { + const auto size = 42u; + const auto tup = std::make_tuple(size); + const auto args = entt::uses_allocator_construction_args>>(std::allocator{}, std::piecewise_construct, std::make_tuple(3), tup); + + static_assert(std::tuple_size_v == 3u); + static_assert(std::is_same_v, std::tuple &>>>); + + ASSERT_EQ(std::get<0>(std::get<2>(args)), size); +} + +TEST(UsesAllocatorConstructionArgs, PairNoArgs) { + [[maybe_unused]] const auto args = entt::uses_allocator_construction_args>>(std::allocator{}); + + static_assert(std::tuple_size_v == 3u); + static_assert(std::is_same_v, std::tuple &>>>); +} + +TEST(UsesAllocatorConstructionArgs, PairValues) { + const auto size = 42u; + const auto args = entt::uses_allocator_construction_args>>(std::allocator{}, 3, size); + + static_assert(std::tuple_size_v == 3u); + static_assert(std::is_same_v, std::tuple &>>>); + + ASSERT_EQ(std::get<0>(std::get<2>(args)), size); +} + +TEST(UsesAllocatorConstructionArgs, PairConstLValueReference) { + const auto value = std::make_pair(3, 42u); + const auto args = entt::uses_allocator_construction_args>>(std::allocator{}, value); + + static_assert(std::tuple_size_v == 3u); + static_assert(std::is_same_v, std::tuple &>>>); + + ASSERT_EQ(std::get<0>(std::get<1>(args)), 3); + ASSERT_EQ(std::get<0>(std::get<2>(args)), 42u); +} + +TEST(UsesAllocatorConstructionArgs, PairRValueReference) { + [[maybe_unused]] const auto args = entt::uses_allocator_construction_args>>(std::allocator{}, std::make_pair(3, 42u)); + + static_assert(std::tuple_size_v == 3u); + static_assert(std::is_same_v, std::tuple &>>>); +} + TEST(MakeObjUsingAllocator, Functionalities) { const auto size = 42u; test::throwing_allocator::trigger_on_allocate = true;