HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
hash_map.hpp
1// Copyright Take Vos 2021-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "../utility/utility.hpp"
8#include "../macros.hpp"
9#include <memory>
10#include <iterator>
11
12
13
14namespace hi::inline v1 {
15namespace detail {
16
18 std::size_t hash;
19};
20
21template<typename Key, typename Value>
23 std::size_t hash;
24 Key key;
25 Value value;
26};
27
28} // namespace detail
29
30template<typename Key, typename T, typename Allocator>
31class hash_map;
32
33template<typename Key, typename Value>
35public:
36 using key_type = Key;
37 using value_type = Value;
38
39 constexpr hash_map_entry() noexcept : _h{0} {}
40 constexpr hash_map_entry(hash_map_entry const&) noexcept = default;
41 constexpr hash_map_entry(hash_map_entry&&) noexcept = default;
42 constexpr hash_map_entry &operator=(hash_map_entry const&) noexcept = default;
43 constexpr hash_map_entry &operator=(hash_map_entry&&) noexcept = default;
44 constexpr ~hash_map_entry()
45 {
46 if (hash() != 0) {
47 std::destroy_at(std::addressof(_hkv));
48 }
49 }
50
51 std::size_t hash() const noexcept
52 {
53 return _h.hash;
54 }
55
56 key_type const &key() const noexcept
57 {
58 return _hkv.key;
59 }
60
61 value_type &value() noexcept
62 {
63 return _hkv.value;
64 }
65
66 value_type const &value() const noexcept
67 {
68 return _hkv.value;
69 }
70
71private:
72 template<typename K, typename V>
73 constexpr void set(std::size_t hash, K &&key, V &&value) noexcept
74 {
75 if (this->hash() != 0) {
76 std::destroy_at(std::addressof(_hkv));
77 }
78 std::construct_at(std::addressof(_hkv), hash, std::forward<K>(key), std::forward<V>(value));
79 }
80
81 template<typename K>
82 constexpr void set(std::size_t hash, K &&key) noexcept
83 {
84 if (this->hash() != 0) {
85 std::destroy_at(std::addressof(_hkv));
86 }
87 std::construct_at(std::addressof(_hkv), hash, std::forward<K>(key), Value{});
88 }
89
90 union {
93 };
94
95 template<typename, typename, typename>
96 friend class hash_map;
97};
98
99template<typename Key, typename T, typename Allocator = std::allocator<hash_map_entry<Key, T>>>
100class hash_map {
101public:
102 using key_type = Key;
103 using value_type = T;
104 using size_type = std::size_t;
105 using difference_type = ptrdiff_t;
106 using allocator_type = Allocator;
107 using reference = value_type &;
108 using const_reference = value_type const &;
109 using pointer = value_type *;
110 using const_pointer = value_type const *;
112 constexpr static bool allocator_always_equal = std::allocator_traits<allocator_type>::is_always_equal::value;
113
114 class iterator {
115 public:
116 constexpr iterator(node_type *node, bool used) noexcept : node(node), used(used) {}
117
118 [[nodiscard]] constexpr node_type &operator*() const noexcept
119 {
120 return *node;
121 }
122
123 [[nodiscard]] constexpr node_type *operator->() const noexcept
124 {
125 return node;
126 }
127
128 [[nodiscard]] constexpr friend bool operator==(iterator const &lhs, std::default_sentinel_t) noexcept
129 {
130 return not lhs.used;
131 }
132
133 private:
134 bool used;
135 node_type *node;
136 };
137
139 public:
140 constexpr const_iterator(node_type *node) noexcept : node(node) {}
141
142 [[nodiscard]] constexpr node_type const &operator*() const noexcept
143 {
144 return *node;
145 }
146
147 [[nodiscard]] constexpr node_type const *operator->() const noexcept
148 {
149 return node;
150 }
151
152 [[nodiscard]] constexpr friend bool operator==(iterator const &lhs, std::default_sentinel_t) noexcept
153 {
154 return node->hash;
155 }
156
157 private:
158 node_type *node;
159 };
160
161 hash_map(hash_map const &) = delete;
162 hash_map(hash_map &&) = delete;
163 hash_map &operator=(hash_map const &) = delete;
164 hash_map &operator=(hash_map &&) = delete;
165
166 constexpr hash_map() noexcept requires(allocator_always_equal) : _nodes(nullptr), _capacity(0), _size(0), allocator()
167 {
168 reserve(initial_capacity);
169 }
170
171 constexpr ~hash_map()
172 {
173 hi_assert_not_null(_nodes);
174 hi_axiom(_capacity != 0);
175
176 std::destroy_n(_nodes, _capacity);
177 std::allocator_traits<allocator_type>::deallocate(allocator, _nodes, _capacity);
178 }
179
180 [[nodiscard]] constexpr allocator_type get_allocator() const noexcept
181 {
182 return allocator;
183 }
184
185 hi_no_inline constexpr void reserve(std::size_t new_capacity) noexcept
186 {
187 if (new_capacity > _capacity) {
188 auto *new_nodes = std::allocator_traits<allocator_type>::allocate(allocator, new_capacity);
189
190 move_nodes(_nodes, _capacity, new_nodes, new_capacity);
191
192 if (_nodes != nullptr) {
193 std::allocator_traits<allocator_type>::deallocate(allocator, _nodes, _capacity);
194 }
195 _nodes = new_nodes;
196 _capacity = new_capacity;
197 }
198 }
199
200 [[nodiscard]] constexpr const_iterator find(key_type const &key) const noexcept
201 {
202 hi_assert_not_null(_nodes);
203 hi_axiom(_capacity != 0);
204
205 auto hash = std::hash<key_type>{}(key);
206 hash = hash == 0 ? 1 : hash;
207
208 auto hash_plus_count = hash;
209 while (true) {
210 // _capacities are selected for their ability to avalanche bad hash values.
211 auto node = _nodes + hash_plus_count % _capacity;
212 if (node->hash() == 0 or (node->hash() == hash and node->key() == key)) {
213 return const_iterator{node};
214 }
215
216 ++hash_plus_count;
217 }
218 }
219
220 template<typename K>
221 [[nodiscard]] constexpr iterator find_or_create(K &&key) noexcept
222 {
223 hi_assert_not_null(_nodes);
224 hi_axiom(_capacity != 0);
225
226 auto hash = std::hash<key_type>{}(key);
227 hash = hash == 0 ? 1 : hash;
228
229 auto hash_plus_count = hash;
230 while (true) {
231 // _capacities are selected for their ability to avalanche bad hash values.
232 auto node = _nodes + hash_plus_count % _capacity;
233 hi_axiom_not_null(node);
234 if (node->hash() == hash and node->key() == key) {
235 return iterator{node, true};
236
237 } else if (node->hash() == 0) {
238 return or_create(*node, hash, std::forward<K>(key));
239 }
240
241 ++hash_plus_count;
242 }
243 }
244
245 template<typename K>
246 [[nodiscard]] constexpr value_type &operator[](K &&key) noexcept
247 {
248 return find_or_create(std::forward<K>(key))->value();
249 }
250
251 [[nodiscard]] constexpr std::default_sentinel_t end() const noexcept
252 {
253 return {};
254 }
255
256 [[nodiscard]] constexpr std::default_sentinel_t cend() const noexcept
257 {
258 return {};
259 }
260
261private:
262 constexpr static std::size_t initial_capacity = 307;
263
264 node_type *_nodes = nullptr;
265 std::size_t _capacity = 0;
266 std::size_t _size = 0;
267 [[no_unique_address]] allocator_type allocator;
268
269 template<typename K>
270 hi_no_inline constexpr iterator or_create(node_type &node, std::size_t hash, K &&key) noexcept
271 {
272 grow_by(1);
273 node.set(hash, std::forward<K>(key));
274 return iterator{&node, false};
275 }
276
282 hi_no_inline constexpr void move_nodes(node_type *src, std::size_t src_size, node_type *dst, std::size_t dst_size) noexcept
283 {
284 hi_axiom_not_null(dst);
285 hilet dst_ = std::uninitialized_value_construct_n(dst, dst_size);
286 hi_axiom_not_null(dst_);
287
288 auto const * const src_last = src + src_size;
289 for (auto src_it = src; src_it != src_last; ++src_it) {
290 hi_axiom_not_null(src_it);
291 hilet hash = src_it->hash();
292
293 if (hash != 0) {
294 auto hash_plus_count = hash;
295 while (true) {
296 hilet dst_it = dst_ + hash_plus_count % dst_size;
297 hi_axiom_not_null(dst_it);
298
299 if (dst_it->hash() == 0) {
300 dst_it->set(hash, std::move(src_it->_hkv.key), std::move(src_it->_hkv.value));
301 break;
302 }
303
304 ++hash_plus_count;
305 }
306 }
307 }
308
309 std::destroy_n(src, src_size);
310 }
311
312 void grow_by(std::size_t nr_entries) noexcept
313 {
314 hi_assert_not_null(_nodes);
315 hi_axiom(_capacity != 0);
316
317 _size += nr_entries;
318
319 // 0.75 fill ratio.
320 hilet max_size = _capacity - (_capacity >> 2);
321 if (_size > max_size) {
322 // Using a growth factor of about 1.5 will allow reallocation in the holes left behind multiple
323 // consecutive grows. Make the new capacity odd, to increase chance for good avalanching.
324 auto new_capacity = (_capacity + (_capacity >> 1) + nr_entries) | 1;
325 reserve(new_capacity);
326 }
327 }
328};
329
330namespace pmr {
331
332template<typename Key, typename T>
334
335}
336} // namespace hi::inline v1
DOXYGEN BUG.
Definition algorithm.hpp:16
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
Definition hash_map.hpp:17
Definition hash_map.hpp:22
Definition hash_map.hpp:100
Definition hash_map.hpp:34
Definition hash_map.hpp:114
Definition hash_map.hpp:138
T addressof(T... args)
T allocate(T... args)
T deallocate(T... args)
T end(T... args)
T find(T... args)
T move(T... args)