Line | Count | Source |
1 | | // |
2 | | // immer: immutable data structures for C++ |
3 | | // Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente |
4 | | // |
5 | | // This software is distributed under the Boost Software License, Version 1.0. |
6 | | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt |
7 | | // |
8 | | |
9 | | #pragma once |
10 | | |
11 | | #include <immer/config.hpp> |
12 | | #include <immer/detail/hamts/champ.hpp> |
13 | | #include <immer/detail/hamts/champ_iterator.hpp> |
14 | | #include <immer/memory_policy.hpp> |
15 | | |
16 | | #include <cassert> |
17 | | #include <functional> |
18 | | #include <stdexcept> |
19 | | |
20 | | namespace immer { |
21 | | |
22 | | template <typename K, |
23 | | typename T, |
24 | | typename Hash, |
25 | | typename Equal, |
26 | | typename MemoryPolicy, |
27 | | detail::hamts::bits_t B> |
28 | | class map_transient; |
29 | | |
30 | | /*! |
31 | | * Immutable unordered mapping of values from type `K` to type `T`. |
32 | | * |
33 | | * @tparam K The type of the keys. |
34 | | * @tparam T The type of the values to be stored in the container. |
35 | | * @tparam Hash The type of a function object capable of hashing |
36 | | * values of type `T`. |
37 | | * @tparam Equal The type of a function object capable of comparing |
38 | | * values of type `T`. |
39 | | * @tparam MemoryPolicy Memory management policy. See @ref |
40 | | * memory_policy. |
41 | | * |
42 | | * @rst |
43 | | * |
44 | | * This container provides a good trade-off between cache locality, |
45 | | * search, update performance and structural sharing. It does so by |
46 | | * storing the data in contiguous chunks of :math:`2^{B}` elements. |
47 | | * When storing big objects, the size of these contiguous chunks can |
48 | | * become too big, damaging performance. If this is measured to be |
49 | | * problematic for a specific use-case, it can be solved by using a |
50 | | * `immer::box` to wrap the type `T`. |
51 | | * |
52 | | * **Example** |
53 | | * .. literalinclude:: ../example/map/intro.cpp |
54 | | * :language: c++ |
55 | | * :start-after: intro/start |
56 | | * :end-before: intro/end |
57 | | * |
58 | | * @endrst |
59 | | * |
60 | | */ |
61 | | template <typename K, |
62 | | typename T, |
63 | | typename Hash = std::hash<K>, |
64 | | typename Equal = std::equal_to<K>, |
65 | | typename MemoryPolicy = default_memory_policy, |
66 | | detail::hamts::bits_t B = default_bits> |
67 | | class map |
68 | | { |
69 | | using value_t = std::pair<K, T>; |
70 | | |
71 | | using move_t = |
72 | | std::integral_constant<bool, MemoryPolicy::use_transient_rvalues>; |
73 | | |
74 | | struct project_value |
75 | | { |
76 | | const T& operator()(const value_t& v) const noexcept |
77 | 7.16k | { |
78 | 7.16k | return v.second; |
79 | 7.16k | } |
80 | | T&& operator()(value_t&& v) const noexcept |
81 | 143k | { |
82 | 143k | return std::move(v.second); |
83 | 143k | } |
84 | | }; |
85 | | |
86 | | struct project_value_ptr |
87 | | { |
88 | | const T* operator()(const value_t& v) const noexcept |
89 | 3.53k | { |
90 | 3.53k | return &v.second; |
91 | 3.53k | } |
92 | | }; |
93 | | |
94 | | struct combine_value |
95 | | { |
96 | | template <typename Kf, typename Tf> |
97 | | value_t operator()(Kf&& k, Tf&& v) const |
98 | 164k | { |
99 | 164k | return {std::forward<Kf>(k), std::forward<Tf>(v)}; |
100 | 164k | } |
101 | | }; |
102 | | |
103 | | struct default_value |
104 | | { |
105 | | const T& operator()() const |
106 | 13.7k | { |
107 | 13.7k | static T v{}; |
108 | 13.7k | return v; |
109 | 13.7k | } |
110 | | }; |
111 | | |
112 | | struct error_value |
113 | | { |
114 | | const T& operator()() const |
115 | | { |
116 | | IMMER_THROW(std::out_of_range{"key not found"}); |
117 | | } |
118 | | }; |
119 | | |
120 | | struct hash_key |
121 | | { |
122 | 13.8M | auto operator()(const value_t& v) { return Hash{}(v.first); } immer::map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, immer::box<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, immer::memory_policy<immer::free_list_heap_policy<immer::cpp_heap, 1024ul>, immer::refcount_policy, immer::spinlock_policy, immer::no_transience_policy, false, true> >, colliding_hash_t, std::__1::equal_to<void>, immer::memory_policy<immer::heap_policy<immer::cpp_heap>, immer::unsafe_refcount_policy, immer::no_lock_policy, immer::no_transience_policy, false, true>, 5u>::hash_key::operator()(std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, immer::box<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, immer::memory_policy<immer::free_list_heap_policy<immer::cpp_heap, 1024ul>, immer::refcount_policy, immer::spinlock_policy, immer::no_transience_policy, false, true> > > const&) Line | Count | Source | 122 | 13.8M | auto operator()(const value_t& v) { return Hash{}(v.first); } |
Unexecuted instantiation: immer::map<int, int, std::__1::hash<int>, std::__1::equal_to<int>, immer::memory_policy<immer::free_list_heap_policy<immer::cpp_heap, 1024ul>, immer::refcount_policy, immer::spinlock_policy, immer::no_transience_policy, false, true>, 5u>::hash_key::operator()(std::__1::pair<int, int> const&) |
123 | | |
124 | | template <typename Key> |
125 | | auto operator()(const Key& v) |
126 | 485k | { |
127 | 485k | return Hash{}(v); |
128 | 485k | } |
129 | | }; |
130 | | |
131 | | struct equal_key |
132 | | { |
133 | | auto operator()(const value_t& a, const value_t& b) |
134 | 363k | { |
135 | 363k | return Equal{}(a.first, b.first); |
136 | 363k | } |
137 | | |
138 | | template <typename Key> |
139 | | auto operator()(const value_t& a, const Key& b) |
140 | 367k | { |
141 | 367k | return Equal{}(a.first, b); |
142 | 367k | } |
143 | | }; |
144 | | |
145 | | struct equal_value |
146 | | { |
147 | | auto operator()(const value_t& a, const value_t& b) |
148 | | { |
149 | | return Equal{}(a.first, b.first) && a.second == b.second; |
150 | | } |
151 | | }; |
152 | | |
153 | | using impl_t = |
154 | | detail::hamts::champ<value_t, hash_key, equal_key, MemoryPolicy, B>; |
155 | | |
156 | | public: |
157 | | using key_type = K; |
158 | | using mapped_type = T; |
159 | | using value_type = std::pair<K, T>; |
160 | | using size_type = detail::hamts::size_t; |
161 | | using difference_type = std::ptrdiff_t; |
162 | | using hasher = Hash; |
163 | | using key_equal = Equal; |
164 | | using reference = const value_type&; |
165 | | using const_reference = const value_type&; |
166 | | |
167 | | using iterator = detail::hamts:: |
168 | | champ_iterator<value_t, hash_key, equal_key, MemoryPolicy, B>; |
169 | | using const_iterator = iterator; |
170 | | |
171 | | using transient_type = map_transient<K, T, Hash, Equal, MemoryPolicy, B>; |
172 | | |
173 | | using memory_policy_type = MemoryPolicy; |
174 | | |
175 | | /*! |
176 | | * Constructs a map containing the elements in `values`. |
177 | | */ |
178 | | map(std::initializer_list<value_type> values) |
179 | | : impl_{impl_t::from_initializer_list(values)} |
180 | | { |
181 | | } |
182 | | |
183 | | /*! |
184 | | * Constructs a map containing the elements in the range |
185 | | * defined by the input iterator `first` and range sentinel `last`. |
186 | | */ |
187 | | template <typename Iter, |
188 | | typename Sent, |
189 | | std::enable_if_t<detail::compatible_sentinel_v<Iter, Sent>, |
190 | | bool> = true> |
191 | | map(Iter first, Sent last) |
192 | | : impl_{impl_t::from_range(first, last)} |
193 | | { |
194 | | } |
195 | | |
196 | | /*! |
197 | | * Default constructor. It creates a map of `size() == 0`. It |
198 | | * does not allocate memory and its complexity is @f$ O(1) @f$. |
199 | | */ |
200 | 16.0k | map() = default; |
201 | | |
202 | | /*! |
203 | | * Returns an iterator pointing at the first element of the |
204 | | * collection. It does not allocate memory and its complexity is |
205 | | * @f$ O(1) @f$. |
206 | | */ |
207 | 8.45k | IMMER_NODISCARD iterator begin() const { return {impl_}; } |
208 | | |
209 | | /*! |
210 | | * Returns an iterator pointing just after the last element of the |
211 | | * collection. It does not allocate and its complexity is @f$ O(1) @f$. |
212 | | */ |
213 | | IMMER_NODISCARD iterator end() const |
214 | 8.45k | { |
215 | 8.45k | return {impl_, typename iterator::end_t{}}; |
216 | 8.45k | } |
217 | | |
218 | | /*! |
219 | | * Returns the number of elements in the container. It does |
220 | | * not allocate memory and its complexity is @f$ O(1) @f$. |
221 | | */ |
222 | | IMMER_NODISCARD size_type size() const { return impl_.size; } |
223 | | |
224 | | /*! |
225 | | * Returns `true` if there are no elements in the container. It |
226 | | * does not allocate memory and its complexity is @f$ O(1) @f$. |
227 | | */ |
228 | | IMMER_NODISCARD bool empty() const { return impl_.size == 0; } |
229 | | |
230 | | /*! |
231 | | * Returns `1` when the key `k` is contained in the map or `0` |
232 | | * otherwise. It won't allocate memory and its complexity is |
233 | | * *effectively* @f$ O(1) @f$. |
234 | | * |
235 | | * This overload participates in overload resolution only if |
236 | | * `Hash::is_transparent` is valid and denotes a type. |
237 | | */ |
238 | | template <typename Key, |
239 | | typename U = Hash, |
240 | | typename = typename U::is_transparent> |
241 | | IMMER_NODISCARD size_type count(const Key& k) const |
242 | | { |
243 | | return impl_.template get<detail::constantly<size_type, 1>, |
244 | | detail::constantly<size_type, 0>>(k); |
245 | | } |
246 | | |
247 | | /*! |
248 | | * Returns `1` when the key `k` is contained in the map or `0` |
249 | | * otherwise. It won't allocate memory and its complexity is |
250 | | * *effectively* @f$ O(1) @f$. |
251 | | */ |
252 | | IMMER_NODISCARD size_type count(const K& k) const |
253 | 291k | { |
254 | 291k | return impl_.template get<detail::constantly<size_type, 1>, |
255 | 291k | detail::constantly<size_type, 0>>(k); |
256 | 291k | } |
257 | | |
258 | | /*! |
259 | | * Returns a `const` reference to the values associated to the key |
260 | | * `k`. If the key is not contained in the map, it returns a |
261 | | * default constructed value. It does not allocate memory and its |
262 | | * complexity is *effectively* @f$ O(1) @f$. |
263 | | * |
264 | | * This overload participates in overload resolution only if |
265 | | * `Hash::is_transparent` is valid and denotes a type. |
266 | | */ |
267 | | template <typename Key, |
268 | | typename U = Hash, |
269 | | typename = typename U::is_transparent> |
270 | | IMMER_NODISCARD const T& operator[](const Key& k) const |
271 | | { |
272 | | return impl_.template get<project_value, default_value>(k); |
273 | | } |
274 | | |
275 | | /*! |
276 | | * Returns a `const` reference to the values associated to the key |
277 | | * `k`. If the key is not contained in the map, it returns a |
278 | | * default constructed value. It does not allocate memory and its |
279 | | * complexity is *effectively* @f$ O(1) @f$. |
280 | | */ |
281 | | IMMER_NODISCARD const T& operator[](const K& k) const |
282 | | { |
283 | | return impl_.template get<project_value, default_value>(k); |
284 | | } |
285 | | |
286 | | /*! |
287 | | * Returns a `const` reference to the values associated to the key |
288 | | * `k`. If the key is not contained in the map, throws an |
289 | | * `std::out_of_range` error. It does not allocate memory and its |
290 | | * complexity is *effectively* @f$ O(1) @f$. |
291 | | */ |
292 | | template <typename Key, |
293 | | typename U = Hash, |
294 | | typename = typename U::is_transparent> |
295 | | const T& at(const Key& k) const |
296 | | { |
297 | | return impl_.template get<project_value, error_value>(k); |
298 | | } |
299 | | |
300 | | /*! |
301 | | * Returns a `const` reference to the values associated to the key |
302 | | * `k`. If the key is not contained in the map, throws an |
303 | | * `std::out_of_range` error. It does not allocate memory and its |
304 | | * complexity is *effectively* @f$ O(1) @f$. |
305 | | * |
306 | | * This overload participates in overload resolution only if |
307 | | * `Hash::is_transparent` is valid and denotes a type. |
308 | | */ |
309 | | const T& at(const K& k) const |
310 | | { |
311 | | return impl_.template get<project_value, error_value>(k); |
312 | | } |
313 | | |
314 | | /*! |
315 | | * Returns a pointer to the value associated with the key `k`. If |
316 | | * the key is not contained in the map, a `nullptr` is returned. |
317 | | * It does not allocate memory and its complexity is *effectively* |
318 | | * @f$ O(1) @f$. |
319 | | * |
320 | | * @rst |
321 | | * |
322 | | * .. admonition:: Why doesn't this function return an iterator? |
323 | | * |
324 | | * Associative containers from the C++ standard library provide a |
325 | | * ``find`` method that returns an iterator pointing to the |
326 | | * element in the container or ``end()`` when the key is missing. |
327 | | * In the case of an unordered container, the only meaningful |
328 | | * thing one may do with it is to compare it with the end, to |
329 | | * test if the find was succesfull, and dereference it. This |
330 | | * comparison is cumbersome compared to testing for a non-empty |
331 | | * optional value. Furthermore, for an immutable container, |
332 | | * returning an iterator would have some additional performance |
333 | | * cost, with no benefits otherwise. |
334 | | * |
335 | | * In our opinion, this function should return a |
336 | | * ``std::optional<const T&>`` but this construction is not valid |
337 | | * in any current standard. As a compromise we return a |
338 | | * pointer, which has similar syntactic properties yet it is |
339 | | * unfortunately unnecessarily unrestricted. |
340 | | * |
341 | | * @endrst |
342 | | */ |
343 | | IMMER_NODISCARD const T* find(const K& k) const |
344 | 5.37k | { |
345 | 5.37k | return impl_.template get<project_value_ptr, |
346 | 5.37k | detail::constantly<const T*, nullptr>>(k); |
347 | 5.37k | } |
348 | | |
349 | | /*! |
350 | | * Returns a pointer to the value associated with the key `k`. If |
351 | | * the key is not contained in the map, a `nullptr` is returned. |
352 | | * It does not allocate memory and its complexity is *effectively* |
353 | | * @f$ O(1) @f$. |
354 | | * |
355 | | * This overload participates in overload resolution only if |
356 | | * `Hash::is_transparent` is valid and denotes a type. |
357 | | */ |
358 | | template <typename Key, |
359 | | typename U = Hash, |
360 | | typename = typename U::is_transparent> |
361 | | IMMER_NODISCARD const T* find(const Key& k) const |
362 | | { |
363 | | return impl_.template get<project_value_ptr, |
364 | | detail::constantly<const T*, nullptr>>(k); |
365 | | } |
366 | | |
367 | | /*! |
368 | | * Returns whether the maps are equal. |
369 | | */ |
370 | | IMMER_NODISCARD bool operator==(const map& other) const |
371 | | { |
372 | | return impl_.template equals<equal_value>(other.impl_); |
373 | | } |
374 | | IMMER_NODISCARD bool operator!=(const map& other) const |
375 | | { |
376 | | return !(*this == other); |
377 | | } |
378 | | |
379 | | /*! |
380 | | * Returns a map containing the association `value`. If the key is |
381 | | * already in the map, it replaces its association in the map. |
382 | | * It may allocate memory and its complexity is *effectively* @f$ |
383 | | * O(1) @f$. |
384 | | */ |
385 | | IMMER_NODISCARD map insert(value_type value) const& |
386 | | { |
387 | | return impl_.add(std::move(value)); |
388 | | } |
389 | | IMMER_NODISCARD decltype(auto) insert(value_type value) && |
390 | | { |
391 | | return insert_move(move_t{}, std::move(value)); |
392 | | } |
393 | | |
394 | | /*! |
395 | | * Returns a map containing the association `(k, v)`. If the key |
396 | | * is already in the map, it replaces its association in the map. |
397 | | * It may allocate memory and its complexity is *effectively* @f$ |
398 | | * O(1) @f$. |
399 | | */ |
400 | | IMMER_NODISCARD map set(key_type k, mapped_type v) const& |
401 | 232k | { |
402 | 232k | return impl_.add({std::move(k), std::move(v)}); |
403 | 232k | } |
404 | | IMMER_NODISCARD decltype(auto) set(key_type k, mapped_type v) && |
405 | 12.9k | { |
406 | 12.9k | return set_move(move_t{}, std::move(k), std::move(v)); |
407 | 12.9k | } |
408 | | |
409 | | /*! |
410 | | * Returns a map replacing the association `(k, v)` by the |
411 | | * association new association `(k, fn(v))`, where `v` is the |
412 | | * currently associated value for `k` in the map or a default |
413 | | * constructed value otherwise. It may allocate memory |
414 | | * and its complexity is *effectively* @f$ O(1) @f$. |
415 | | */ |
416 | | template <typename Fn> |
417 | | IMMER_NODISCARD map update(key_type k, Fn&& fn) const& |
418 | 3.32k | { |
419 | 3.32k | return impl_ |
420 | 3.32k | .template update<project_value, default_value, combine_value>( |
421 | 3.32k | std::move(k), std::forward<Fn>(fn)); |
422 | 3.32k | } |
423 | | template <typename Fn> |
424 | | IMMER_NODISCARD decltype(auto) update(key_type k, Fn&& fn) && |
425 | 160k | { |
426 | 160k | return update_move(move_t{}, std::move(k), std::forward<Fn>(fn)); |
427 | 160k | } |
428 | | |
429 | | /*! |
430 | | * Returns a map replacing the association `(k, v)` by the association new |
431 | | * association `(k, fn(v))`, where `v` is the currently associated value for |
432 | | * `k` in the map. It does nothing if `k` is not present in the map. It |
433 | | * may allocate memory and its complexity is *effectively* @f$ O(1) @f$. |
434 | | */ |
435 | | template <typename Fn> |
436 | | IMMER_NODISCARD map update_if_exists(key_type k, Fn&& fn) const& |
437 | | { |
438 | | return impl_.template update_if_exists<project_value, combine_value>( |
439 | | std::move(k), std::forward<Fn>(fn)); |
440 | | } |
441 | | template <typename Fn> |
442 | | IMMER_NODISCARD decltype(auto) update_if_exists(key_type k, Fn&& fn) && |
443 | | { |
444 | | return update_if_exists_move( |
445 | | move_t{}, std::move(k), std::forward<Fn>(fn)); |
446 | | } |
447 | | |
448 | | /*! |
449 | | * Returns a map without the key `k`. If the key is not |
450 | | * associated in the map it returns the same map. It may allocate |
451 | | * memory and its complexity is *effectively* @f$ O(1) @f$. |
452 | | */ |
453 | 14.6k | IMMER_NODISCARD map erase(const K& k) const& { return impl_.sub(k); } |
454 | | IMMER_NODISCARD decltype(auto) erase(const K& k) && |
455 | 10.4k | { |
456 | 10.4k | return erase_move(move_t{}, k); |
457 | 10.4k | } |
458 | | |
459 | | /*! |
460 | | * Returns a @a transient form of this container, an |
461 | | * `immer::map_transient`. |
462 | | */ |
463 | | IMMER_NODISCARD transient_type transient() const& |
464 | | { |
465 | | return transient_type{impl_}; |
466 | | } |
467 | | IMMER_NODISCARD transient_type transient() && |
468 | | { |
469 | | return transient_type{std::move(impl_)}; |
470 | | } |
471 | | |
472 | | /*! |
473 | | * Returns a value that can be used as identity for the container. If two |
474 | | * values have the same identity, they are guaranteed to be equal and to |
475 | | * contain the same objects. However, two equal containers are not |
476 | | * guaranteed to have the same identity. |
477 | | */ |
478 | | void* identity() const { return impl_.root; } |
479 | | |
480 | | // Semi-private |
481 | 366k | const impl_t& impl() const { return impl_; } |
482 | | |
483 | | private: |
484 | | friend transient_type; |
485 | | |
486 | | map&& insert_move(std::true_type, value_type value) |
487 | | { |
488 | | impl_.add_mut({}, std::move(value)); |
489 | | return std::move(*this); |
490 | | } |
491 | | map insert_move(std::false_type, value_type value) |
492 | | { |
493 | | return impl_.add(std::move(value)); |
494 | | } |
495 | | |
496 | | map&& set_move(std::true_type, key_type k, mapped_type m) |
497 | 12.9k | { |
498 | 12.9k | impl_.add_mut({}, {std::move(k), std::move(m)}); |
499 | 12.9k | return std::move(*this); |
500 | 12.9k | } |
501 | | map set_move(std::false_type, key_type k, mapped_type m) |
502 | | { |
503 | | return impl_.add({std::move(k), std::move(m)}); |
504 | | } |
505 | | |
506 | | template <typename Fn> |
507 | | map&& update_move(std::true_type, key_type k, Fn&& fn) |
508 | 160k | { |
509 | 160k | impl_.template update_mut<project_value, default_value, combine_value>( |
510 | 160k | {}, std::move(k), std::forward<Fn>(fn)); |
511 | 160k | return std::move(*this); |
512 | 160k | } |
513 | | template <typename Fn> |
514 | | map update_move(std::false_type, key_type k, Fn&& fn) |
515 | | { |
516 | | return impl_ |
517 | | .template update<project_value, default_value, combine_value>( |
518 | | std::move(k), std::forward<Fn>(fn)); |
519 | | } |
520 | | |
521 | | template <typename Fn> |
522 | | map&& update_if_exists_move(std::true_type, key_type k, Fn&& fn) |
523 | | { |
524 | | impl_.template update_if_exists_mut<project_value, combine_value>( |
525 | | {}, std::move(k), std::forward<Fn>(fn)); |
526 | | return std::move(*this); |
527 | | } |
528 | | template <typename Fn> |
529 | | map update_if_exists_move(std::false_type, key_type k, Fn&& fn) |
530 | | { |
531 | | return impl_.template update_if_exists<project_value, combine_value>( |
532 | | std::move(k), std::forward<Fn>(fn)); |
533 | | } |
534 | | |
535 | | map&& erase_move(std::true_type, const key_type& value) |
536 | 10.4k | { |
537 | 10.4k | impl_.sub_mut({}, value); |
538 | 10.4k | return std::move(*this); |
539 | 10.4k | } |
540 | | map erase_move(std::false_type, const key_type& value) |
541 | | { |
542 | | return impl_.sub(value); |
543 | | } |
544 | | |
545 | | // for immer::persist |
546 | | public: |
547 | | map(impl_t impl) |
548 | 250k | : impl_(std::move(impl)) |
549 | 250k | { |
550 | 250k | } |
551 | | |
552 | | private: |
553 | | impl_t impl_ = impl_t::empty(); |
554 | | }; |
555 | | |
556 | | static_assert(std::is_nothrow_move_constructible<map<int, int>>::value, |
557 | | "map is not nothrow move constructible"); |
558 | | static_assert(std::is_nothrow_move_assignable<map<int, int>>::value, |
559 | | "map is not nothrow move assignable"); |
560 | | |
561 | | } // namespace immer |