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