From 606ec9054f63c8ecd15b18fc8acf2915ecc3ff23 Mon Sep 17 00:00:00 2001 From: isidorostsa Date: Wed, 27 Mar 2024 15:48:56 -0500 Subject: [PATCH] Add inplace_vector as specialization of small_vector --- .../datastructures/detail/small_vector.hpp | 107 ++++++++++++++++-- 1 file changed, 99 insertions(+), 8 deletions(-) diff --git a/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp b/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp index d6379378d138..ebb890e5b288 100644 --- a/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp +++ b/libs/core/datastructures/include/hpx/datastructures/detail/small_vector.hpp @@ -218,7 +218,7 @@ namespace hpx::detail { // note: Allocator is currently unused template > + typename Allocator = std::allocator, bool emulate_inplace_vector = false> class small_vector { static_assert(MinInlineCapacity <= 127, @@ -249,7 +249,8 @@ namespace hpx::detail { [[nodiscard]] constexpr auto is_direct() const noexcept -> bool { - return (m_data[0] & 1U) != 0U; + if constexpr (emulate_inplace_vector) return true; + else return (m_data[0] & 1U) != 0U; } [[nodiscard]] auto indirect() noexcept -> storage* @@ -310,6 +311,7 @@ namespace hpx::detail { void realloc(std::size_t new_capacity) { + static_assert(!emulate_inplace_vector, "If called in an inplace_vector, it is a bug."); if (new_capacity <= N) { // put everything into direct storage @@ -539,9 +541,17 @@ namespace hpx::detail { template void assign(It first, It last, std::forward_iterator_tag /*unused*/) { + auto s = std::distance(first, last); + if constexpr (emulate_inplace_vector) + { + // Can not have an inplace_vector with a size larger than N + if (s <= capacity()) { + throw std::bad_alloc(); + } + } + clear(); - auto s = std::distance(first, last); reserve(s); std::uninitialized_copy(first, last, data()); set_size(s); @@ -577,7 +587,7 @@ namespace hpx::detail { // * source_begin <= target_begin // * source_end onwards is uninitialized memory // - // Destroys then empty elements in [source_begin, source_end) + // Destroys the empty elements in [source_begin, source_end) auto shift_right( T* source_begin, T* source_end, T* target_begin) noexcept { @@ -594,7 +604,7 @@ namespace hpx::detail { std::destroy(source_begin, (std::min)(source_end, target_begin)); } - // makes space for uninitialized data of cout elements. Also updates + // makes space for uninitialized data of count elements. Also updates // size. template [[nodiscard]] auto make_uninitialized_space_new( @@ -710,12 +720,26 @@ namespace hpx::detail { std::size_t count, T const& value, Allocator const& = Allocator()) : small_vector() { + if constexpr (emulate_inplace_vector) + { + // Can not have an inplace_vector with a size larger than N + if (count > N) { + throw std::bad_alloc(); + } + } resize(count, value); } explicit small_vector(std::size_t count, Allocator const& = Allocator()) : small_vector() { + if constexpr (emulate_inplace_vector) + { + // Can not have an inplace_vector with a size larger than N + if (count > N) { + throw std::bad_alloc(); + } + } reserve(count); if (is_direct()) { @@ -741,7 +765,14 @@ namespace hpx::detail { : small_vector() { auto s = other.size(); - reserve(s); + // If both vectors are direct with the same capacity it will fit + // without further allocations needed. + + if constexpr (!emulate_inplace_vector) + { + reserve(s); + } + std::uninitialized_copy(other.begin(), other.end(), begin()); set_size(s); } @@ -816,6 +847,13 @@ namespace hpx::detail { void resize(std::size_t count) { + if constexpr (emulate_inplace_vector) { + // Static vector cannot be resized beyond capacity + if (count > N) { + throw std::bad_alloc(); + } + } + if (count > capacity()) { reserve(count); @@ -833,6 +871,13 @@ namespace hpx::detail { void resize(std::size_t count, value_type const& value) { + if constexpr (emulate_inplace_vector) { + // Static vector cannot resize beyond capacity + if (count > N) { + throw std::bad_alloc(); + } + } + if (count > capacity()) { reserve(count); @@ -850,6 +895,12 @@ namespace hpx::detail { auto reserve(std::size_t s) { + if constexpr (emulate_inplace_vector) { + // Static vector cannot reserve beyond capacity + if (s > N) { + throw std::bad_alloc(); + } + } auto const old_capacity = capacity(); auto const new_capacity = calculate_new_capacity(s, old_capacity); if (new_capacity > old_capacity) @@ -860,6 +911,9 @@ namespace hpx::detail { [[nodiscard]] constexpr auto capacity() const noexcept -> std::size_t { + if constexpr (emulate_inplace_vector) { + return capacity(); + } if (is_direct()) { return capacity(); @@ -898,7 +952,15 @@ namespace hpx::detail { if (is_direct()) { s = direct_size(); - if (s < N) + if constexpr (emulate_inplace_vector) { + // Exceeded static_storage + if (s + 1 > N) { + throw std::bad_alloc(); + } + } + + // avoid double-checking for inplace_vector + if (emulate_inplace_vector || s < N) { set_direct_and_size(s + 1); return *hpx::construct_at( @@ -1097,6 +1159,7 @@ namespace hpx::detail { [[nodiscard]] static constexpr auto max_size() -> std::size_t { + if constexpr (emulate_inplace_vector) return N; return (std::numeric_limits::max)(); } @@ -1110,6 +1173,12 @@ namespace hpx::detail { { // per the standard we wouldn't need to do anything here. But since // we are so nice, let's do the shrink. + + if constexpr (emulate_inplace_vector) { + // Can not change the capacity of a static vector so noop + return; + } + auto const c = capacity(); auto const s = size(); if (s >= c) @@ -1130,6 +1199,12 @@ namespace hpx::detail { template auto emplace(const_iterator pos, Args&&... args) -> iterator { + if constexpr(emulate_inplace_vector) { + // it will be expanded by one element + if (direct_size() + 1 > N) { + throw std::bad_alloc(); + } + } auto* p = make_uninitialized_space(pos, 1); return hpx::construct_at( static_cast(p), HPX_FORWARD(Args, args)...); @@ -1148,6 +1223,11 @@ namespace hpx::detail { auto insert(const_iterator pos, std::size_t count, T const& value) -> iterator { + if constexpr(emulate_inplace_vector) { + if (direct_size() + count > N) { + throw std::bad_alloc(); + } + } auto* p = make_uninitialized_space(pos, count); std::uninitialized_fill_n(p, count, value); return p; @@ -1170,6 +1250,8 @@ namespace hpx::detail { auto s = size(); while (first != last) { + // if we are emulating inplace_vector, the out-of-bounds + // emplacement is caught in emplace_back emplace_back(*first); ++first; } @@ -1185,7 +1267,13 @@ namespace hpx::detail { auto insert(const_iterator pos, It first, It last, std::forward_iterator_tag /*unused*/) { - auto* p = make_uninitialized_space(pos, std::distance(first, last)); + auto d = std::distance(first, last); + if constexpr(emulate_inplace_vector) { + if (direct_size() + d > N) { + throw std::bad_alloc(); + } + } + auto* p = make_uninitialized_space(pos, d); std::uninitialized_copy(first, last, p); return p; } @@ -1262,6 +1350,9 @@ namespace hpx::detail { { return !(a > b); } + + template > + using inplace_vector = small_vector; } // namespace hpx::detail // NOLINTNEXTLINE(cert-dcl58-cpp)