From 9fb78471787ae0cf4709fecda2786bcd0056b3f2 Mon Sep 17 00:00:00 2001 From: Stephan Dollberg Date: Thu, 21 Mar 2024 13:46:17 +0000 Subject: [PATCH] segment_map: Allow also specifying a custom container for the bucket array The previous patch allowed using the bucket array in segmented mode. This patch expands the series to similarly also allow using a custom container. We are only allowing specifying a custom container and derive a possible custom allocator directly by rebinding the value allocator. This guarantees that they can be converted to each other. --- include/ankerl/unordered_dense.h | 95 +++++++++++++++++++++++--------- test/app/doctest.h | 19 ++++--- test/unit/custom_container.cpp | 4 +- 3 files changed, 83 insertions(+), 35 deletions(-) diff --git a/include/ankerl/unordered_dense.h b/include/ankerl/unordered_dense.h index 40bfe452..fd81e96a 100644 --- a/include/ankerl/unordered_dense.h +++ b/include/ankerl/unordered_dense.h @@ -436,6 +436,7 @@ ANKERL_UNORDERED_DENSE_PACK(struct big { namespace detail { struct nonesuch {}; +struct default_container_t {}; template class Op, class... Args> struct detector { @@ -796,6 +797,7 @@ template class table : public std::conditional_t, base_table_type_map, base_table_type_set> { using underlying_value_type = typename std::conditional_t, std::pair, Key>; @@ -810,9 +812,13 @@ class table : public std::conditional_t, base_table_type_map, bas private: using bucket_alloc = typename std::allocator_traits::template rebind_alloc; - using underlying_bucket_type = + using default_bucket_container_type = std::conditional_t, std::vector>; + using bucket_container_type = std::conditional_t, + default_bucket_container_type, + BucketContainer>; + static constexpr uint8_t initial_shifts = 64 - 2; // 2^(64-m_shift) number of buckets static constexpr float default_max_load_factor = 0.8F; @@ -840,7 +846,7 @@ class table : public std::conditional_t, base_table_type_map, bas static_assert(std::is_trivially_copyable_v, "assert we can just memset / memcpy"); value_container_type m_values{}; // Contains all the key-value pairs in one densely stored container. No holes. - underlying_bucket_type m_buckets{}; + bucket_container_type m_buckets{}; size_t m_max_bucket_capacity = 0; float m_max_load_factor = default_max_load_factor; Hash m_hash{}; @@ -854,11 +860,11 @@ class table : public std::conditional_t, base_table_type_map, bas } // Helper to access bucket through pointer types - [[nodiscard]] static constexpr auto at(underlying_bucket_type& bucket, size_t offset) -> Bucket& { + [[nodiscard]] static constexpr auto at(bucket_container_type& bucket, size_t offset) -> Bucket& { return bucket[offset]; } - [[nodiscard]] static constexpr auto at(const underlying_bucket_type& bucket, size_t offset) -> const Bucket& { + [[nodiscard]] static constexpr auto at(const bucket_container_type& bucket, size_t offset) -> const Bucket& { return bucket[offset]; } @@ -949,7 +955,7 @@ class table : public std::conditional_t, base_table_type_map, bas } else { m_shifts = other.m_shifts; allocate_buckets_from_shift(); - if constexpr (IsSegmented) { + if constexpr (IsSegmented || !std::is_same_v) { for (auto i = 0UL; i < bucket_count(); ++i) { at(m_buckets, i) = at(other.m_buckets, i); } @@ -974,8 +980,10 @@ class table : public std::conditional_t, base_table_type_map, bas void allocate_buckets_from_shift() { auto num_buckets = calc_num_buckets(m_shifts); - if constexpr (IsSegmented) { - m_buckets.reserve(num_buckets); + if constexpr (IsSegmented || !std::is_same_v) { + if constexpr (has_reserve) { + m_buckets.reserve(num_buckets); + } for (size_t i = m_buckets.size(); i < num_buckets; ++i) { m_buckets.emplace_back(); } @@ -991,7 +999,7 @@ class table : public std::conditional_t, base_table_type_map, bas } void clear_buckets() { - if constexpr (IsSegmented) { + if constexpr (IsSegmented || !std::is_same_v) { for (auto&& e : m_buckets) { std::memset(&e, 0, sizeof(e)); } @@ -1019,7 +1027,7 @@ class table : public std::conditional_t, base_table_type_map, bas on_error_bucket_overflow(); } --m_shifts; - if constexpr (!IsSegmented) { + if constexpr (!IsSegmented || std::is_same_v) { deallocate_buckets(); } allocate_buckets_from_shift(); @@ -1939,30 +1947,34 @@ ANKERL_UNORDERED_DENSE_EXPORT template , class KeyEqual = std::equal_to, class AllocatorOrContainer = std::allocator>, - class Bucket = bucket_type::standard> -using map = detail::table; + class Bucket = bucket_type::standard, + class BucketContainer = detail::default_container_t> +using map = detail::table; ANKERL_UNORDERED_DENSE_EXPORT template , class KeyEqual = std::equal_to, class AllocatorOrContainer = std::allocator>, - class Bucket = bucket_type::standard> -using segmented_map = detail::table; + class Bucket = bucket_type::standard, + class BucketContainer = detail::default_container_t> +using segmented_map = detail::table; ANKERL_UNORDERED_DENSE_EXPORT template , class KeyEqual = std::equal_to, class AllocatorOrContainer = std::allocator, - class Bucket = bucket_type::standard> -using set = detail::table; + class Bucket = bucket_type::standard, + class BucketContainer = detail::default_container_t> +using set = detail::table; ANKERL_UNORDERED_DENSE_EXPORT template , class KeyEqual = std::equal_to, class AllocatorOrContainer = std::allocator, - class Bucket = bucket_type::standard> -using segmented_set = detail::table; + class Bucket = bucket_type::standard, + class BucketContainer = detail::default_container_t> +using segmented_set = detail::table; # if defined(ANKERL_UNORDERED_DENSE_PMR) @@ -1973,29 +1985,54 @@ ANKERL_UNORDERED_DENSE_EXPORT template , class KeyEqual = std::equal_to, class Bucket = bucket_type::standard> -using map = - detail::table>, Bucket, false>; +using map = detail::table>, + Bucket, + detail::default_container_t, + false>; ANKERL_UNORDERED_DENSE_EXPORT template , class KeyEqual = std::equal_to, class Bucket = bucket_type::standard> -using segmented_map = - detail::table>, Bucket, true>; +using segmented_map = detail::table>, + Bucket, + detail::default_container_t, + true>; ANKERL_UNORDERED_DENSE_EXPORT template , class KeyEqual = std::equal_to, class Bucket = bucket_type::standard> -using set = detail::table, Bucket, false>; +using set = detail::table, + Bucket, + detail::default_container_t, + false>; ANKERL_UNORDERED_DENSE_EXPORT template , class KeyEqual = std::equal_to, class Bucket = bucket_type::standard> -using segmented_set = - detail::table, Bucket, true>; +using segmented_set = detail::table, + Bucket, + detail::default_container_t, + true>; } // namespace pmr @@ -2020,11 +2057,15 @@ ANKERL_UNORDERED_DENSE_EXPORT template // NOLINTNEXTLINE(cert-dcl58-cpp) -auto erase_if(ankerl::unordered_dense::detail::table& map, - Pred pred) -> size_t { - using map_t = ankerl::unordered_dense::detail::table; +auto erase_if( + ankerl::unordered_dense::detail::table& + map, + Pred pred) -> size_t { + using map_t = ankerl::unordered_dense::detail:: + table; // going back to front because erase() invalidates the end iterator auto const old_size = map.size(); diff --git a/test/app/doctest.h b/test/app/doctest.h index 446f7163..2e165818 100644 --- a/test/app/doctest.h +++ b/test/app/doctest.h @@ -29,9 +29,12 @@ template , class KeyEqual = std::equal_to, class AllocatorOrContainer = std::deque>, - class Bucket = ankerl::unordered_dense::bucket_type::standard> -class deque_map : public ankerl::unordered_dense::detail::table { - using base_t = ankerl::unordered_dense::detail::table; + class Bucket = ankerl::unordered_dense::bucket_type::standard, + class BucketContainer = std::deque> +class deque_map : public ankerl::unordered_dense::detail:: + table { + using base_t = + ankerl::unordered_dense::detail::table; using base_t::base_t; }; @@ -39,10 +42,12 @@ template , class KeyEqual = std::equal_to, class AllocatorOrContainer = std::deque, - class Bucket = ankerl::unordered_dense::bucket_type::standard> -class deque_set - : public ankerl::unordered_dense::detail::table { - using base_t = ankerl::unordered_dense::detail::table; + class Bucket = ankerl::unordered_dense::bucket_type::standard, + class BucketContainer = std::deque> +class deque_set : public ankerl::unordered_dense::detail:: + table { + using base_t = ankerl::unordered_dense::detail:: + table; using base_t::base_t; }; diff --git a/test/unit/custom_container.cpp b/test/unit/custom_container.cpp index 84848ffb..7ee79ef4 100644 --- a/test/unit/custom_container.cpp +++ b/test/unit/custom_container.cpp @@ -15,7 +15,9 @@ TEST_CASE_MAP("custom_container", std::string, ankerl::unordered_dense::hash, std::equal_to, - std::deque>) { + std::deque>, + ankerl::unordered_dense::bucket_type::standard, + std::deque) { auto map = map_t(); for (int i = 0; i < 10; ++i) {