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