Flow 1.0.2
Flow project: Full implementation reference.
linked_hash_map.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
22#include <cstddef>
23#include <boost/unordered_map.hpp>
24#include <boost/move/unique_ptr.hpp>
25
26namespace flow::util
27{
28
29/**
30 * An object of this class is a map that combines the lookup speed of a `boost::unordered_map<>` and ordering and
31 * iterator stability capabilities of an `std::list<>`.
32 *
33 * The API is generally that of an `unordered_map<>`. The differences essentially all have to do with iterators.
34 * This map introduces a concept of "newness," which determines the iteration order. Moreover, *every* iterator remains
35 * valid except (of course) under erasure of the underlying element. Newness is defined as follows inductively:
36 * whenever an element is inserted, it is "newest," thus it is placed at the front of the iterator order. Furthermore,
37 * the methods touch() can be used to make any element "newest" (moved to the front of the iterator order). Iterator
38 * thus formed orders elements from newest to oldest (hence newest() is begin(), past_oldest() is end()).
39 *
40 * Performance expectations: The best way to determine a method's time needs is to
41 * imagine what it must do. If it must perform a lookup by key, that is an `unordered_map<>` lookup resulting in an
42 * (at least internal) iterator. If it must insert an element, it is always inserted at the start of a `list`; and
43 * also into an `unordered_map<>`. If it must erase an element based on an iterator, that element is erased from a list
44 * based on that iterator; and also by key from an `unordered_map<>`. Iteration itself is iteration along a `list`.
45 * But essentially, every operation is either near constant time or constant time.
46 * In terms of space needs, this essentially stores the values themselves in a `list`; and also a copy of each key
47 * in an `unordered_map<>`, which also stores a pointer or list iterator per element.
48 *
49 * ### Thread safety ###
50 * Same as for `unordered_map<>`.
51 *
52 * @tparam Key
53 * Key type. Same requirements and behavior as `unordered_map<>` counterpart. Also (is it "in particular"?):
54 * `Key` must be Assignable, which is STL-speak for: If `Key x, y` are objects of this type,
55 * then `x = y;` is valid and has all the usual semantics. (There are other requirements, but
56 * that's the "controversial" one of interest.) In particular, `Key` cannot be of the form `T const` --
57 * more commonly written as `const T` (but recall that, say, `const char*` = `char const *` really;
58 * which is therefore fine here).
59 * @tparam Mapped
60 * The 2nd (satellite) part of the `Value` pair type. Same requirements and behavior as `unordered_map<>`
61 * counterpart. Colloquially, in a K->V map, this is V, while formally the values stored are (K, V)
62 * pairs.
63 * @tparam Hash
64 * Hasher type. Same requirements and behavior as `unordered_map<>` counterpart. To get a hasher
65 * object, one must be able to call: `Hash h = Hash()`. To then hash a `Key`, one must be able to
66 * call `h(key)`. Typically one will simply define a `size_t hash_value(Key)` function, which will be
67 * activated via the default value for this template parameter. Defaults to `boost::hash<Key>`.
68 * @tparam Pred
69 * Equality functor type. Same requirements and behavior as `unordered_map<>` counterpart.
70 * Once a functor object `Pred e = Pred()` is obtained, `bool eq = e(a, b)` must return whether `a` equals
71 * `b`, where `a` and `b` are keys. Typically `operator==()` will be used via the default template parameter.
72 * Defaults to `std::equal_to<Key>`.
73 */
74template<typename Key, typename Mapped, typename Hash, typename Pred>
76{
77public:
78 // Types.
79
80 /// Short-hand for key/mapped-value pairs stored in the structure.
81 using Value = std::pair<Key const, Mapped>;
82
83private:
84 // Types. These are here in the middle of public block due to inability to forward-declare aliases.
85
86 /// Short-hand for doubly linked list of (`Key`, `Mapped`) pairs.
87 using Value_list = std::list<Value>;
88
89public:
90
91 // Types (continued).
92
93 /// Type for index into array of items, where items are all applicable objects including `Value`s and `Key`s.
94 using size_type = std::size_t;
95 /// Type for difference of `size_type`s.
96 using difference_type = std::ptrdiff_t;
97
98 /// Type for iterator pointing into a mutable structure of this type.
99 using Iterator = typename Value_list::iterator;
100
101 /// Type for iterator pointing into an immutable structure of this type.
102 using Const_iterator = typename Value_list::const_iterator;
103
104 /// Type for reverse iterator pointing into a mutable structure of this type.
105 using Reverse_iterator = typename Value_list::reverse_iterator;
106
107 /// Type for reverse iterator pointing into an immutable structure of this type.
108 using Const_reverse_iterator = typename Value_list::const_reverse_iterator;
109
110 /// For container compliance (hence the irregular capitalization): `Key` type.
111 using key_type = Key;
112 /// For container compliance (hence the irregular capitalization): `Mapped` type.
113 using mapped_type = Mapped;
114 /// For container compliance (hence the irregular capitalization): `Key`/`Mapped` pair type.
116 /// For container compliance (hence the irregular capitalization): `Hash` type.
117 using hasher = Hash;
118 /// For container compliance (hence the irregular capitalization): `Pred` type.
119 using key_equal = Pred;
120 /// For container compliance (hence the irregular capitalization): pointer to `Key`/`Mapped` pair type.
121 using pointer = Value*;
122 /// For container compliance (hence the irregular capitalization): pointer to `const Key`/`Mapped` pair type.
123 using const_pointer = Value const *;
124 /// For container compliance (hence the irregular capitalization): reference to `Key`/`Mapped` pair type.
125 using reference = Value&;
126 /// For container compliance (hence the irregular capitalization): reference to `const Key`/`Mapped` pair type.
127 using const_reference = Value const &;
128 /// For container compliance (hence the irregular capitalization): #Iterator type.
130 /// For container compliance (hence the irregular capitalization): #Const_iterator type.
132
133 // Constructors/destructor.
134
135 /**
136 * Constructs empty structure with some basic parameters.
137 *
138 * @param n_buckets
139 * Number of buckets for the unordered (hash) table. Special value -1 (default) will cause us to use
140 * whatever `unordered_map<>` would use by default.
141 * @param hasher_instance
142 * Instance of the hash function type (`hasher_instance(Key k)` should be `size_type`d hash of key `k`).
143 * @param key_equal_instance
144 * Instance of the equality function type (`key_equal_instance(Key k1, Key k2)` should return `true` if and
145 * only if `k1` equals `k2`).
146 */
147 explicit Linked_hash_map(size_type n_buckets = size_type(-1),
148 Hash const & hasher_instance = Hash(),
149 Pred const & key_equal_instance = Pred());
150
151 /**
152 * Constructs structure with some basic parameters, and values initialized from initializer list.
153 * The values are inserted as if `insert(v)` was called for each pair `v` in `values`
154 * <em>in reverse order</em>. Since the canonical ordering places the *newest* (last inserted/touch()ed)
155 * element at the *front* of the ordering, that means that forward iteration through the set (right after this
156 * constructor runs) will yield values in the *same* order as in initializer list `values`.
157 *
158 * @param values
159 * Values with which to fill the structure after initializing it.
160 * Typically you'd provide a series of key/value pairs like this:
161 * `{{ key1, value1 }, { key2, value2 }, ...}`. They will appear in iterated sequence in the same order as
162 * they appear in this list.
163 * @param n_buckets
164 * See other constructor.
165 * @param hasher_instance
166 * See other constructor.
167 * @param key_equal_instance
168 * See other constructor.
169 */
170 explicit Linked_hash_map(std::initializer_list<Value> values,
171 size_type n_buckets = size_type(-1),
172 Hash const & hasher_instance = Hash(),
173 Pred const & key_equal_instance = Pred());
174
175 /**
176 * Constructs object that is a copy of the given source. Equivalent to `operator=(src)`.
177 *
178 * @param src
179 * Source object.
180 */
182
183 /**
184 * Constructs object by making it equal to the given source, while the given source becomes as-if default-cted.
185 *
186 * @param src
187 * Source object which is emptied.
188 */
190
191 // Methods.
192
193 /**
194 * Overwrites this object with a copy of the given source. We become equal to `src` but independent of it to the max
195 * extent possible (if you've got pointers stored in there, for example, the pointers are copied, not the values
196 * at those pointers). In addition, the hasher instance and equality predicate are copied from `src`. Finally, a
197 * reasonable attempt is made to also make the internal structure of the hash map to be similar to that of `src.
198 *
199 * @param src
200 * Source object.
201 * @return `*this`.
202 */
204
205 /**
206 * Overwrites this object making it equal to the given source, while the given source becomes as-if default-cted.
207 *
208 * @param src
209 * Source object which is emptied (unless it *is* `*this`; then no-op).
210 * @return `*this`.
211 */
213
214 /**
215 * Attempts to insert the given key/mapped-value pair into the map. If the key is already present in the map,
216 * does nothing. Return value indicates various info of interest about what occurred or did not occur.
217 * Key presence is determined according to the `Pred` template parameter which determines equality of 2 given keys;
218 * and via the `Hash` template parameter that enables efficient hash-based lookup.
219 * If inserted, the new element is considered "newest," as if by touch(). If not inserted, the existing element
220 * location is not affected.
221 *
222 * @param key_and_mapped
223 * The key/mapped-value pair to attempt to insert. This value is copied, and the copy is inserted.
224 * @return A pair whose second element is true if and only if the insertion occurred; and whose first element
225 * is an iterator pointing to either the newly inserted element or already present one with a key equal to
226 * `key_and_mapped.first`.
227 */
228 std::pair<Iterator, bool> insert(Value const & key_and_mapped);
229
230 /**
231 * Attempts to find value at the given key in the map. Key presence is determined according to the `Pred` template
232 * parameter which determines equality of 2 given keys; and via the `Hash` template parameter that enables efficient
233 * hash-based lookup. The returned iterator (if valid) can be used to mutate the elements inside the map.
234 *
235 * As long as the value is not removed from the map, the reference will continue to be valid.
236 *
237 * Any subsequent writes to the referred to (by returned iterator) area of memory will NOT have the effect of touch().
238 * If you need it, call touch() yourself.
239 *
240 * @param key
241 * Key whose equal to find.
242 * @return If found, iterator to the key/mapped-value pair with the equivalent key; else `this->end()`.
243 */
244 Iterator find(Key const & key);
245
246 /**
247 * Attempts to find value at the given key in the map. Key presence is determined according to the `Pred` template
248 * parameter which determines equality of 2 given keys; and via the `Hash` template parameter that enables efficient
249 * hash-based lookup. The returned iterator (if valid) cannot be used to mutate the elements inside the map.
250 *
251 * As long as the value is not removed from the map, the iterator will continue to be valid.
252 *
253 * @param key
254 * Key whose equal to find.
255 * @return If found, iterator to the key/mapped-value pair with the equivalent key; else `this->const_past_oldest()`.
256 */
257 Const_iterator find(Key const & key) const;
258
259 /**
260 * Returns the number of times a key is equivalent to the given one is present in the map: either 1 or 0.
261 *
262 * @param key
263 * Key whose equal to find.
264 * @return 0 or 1.
265 */
266 size_type count(Key const & key) const;
267
268 /**
269 * Equivalent to `insert(Value(key, Mapped())).first->second` (but avoids unnecessarily invoking `Mapped()`/generally
270 * strives for better performance). Less formally, it either finds the value at the given key, or if not found
271 * inserts one with a default-constructed value; then returns reference to the in-structure stored `Mapped` value
272 * which can be used to to read and/or modify that value directly.
273 *
274 * Note that if `Mapped& x` is returned, then although `x` is mutable, in actuality `x.first` is `const`; so only
275 * `x.second` is truly mutable. You must not write to the key (such as via a `const_cast<>`); doing so will result
276 * in undefined behavior.
277 *
278 * If inserted, the new element is considered "newest," as if by touch(). If not inserted, the existing element
279 * location is not affected.
280 *
281 * As long as the value is not removed from the map, the reference will continue to be valid.
282 *
283 * Any subsequent writes to the referred to area of memory will NOT have the effect of touch(). If you need it,
284 * call touch() yourself.
285 *
286 * @param key
287 * Key whose equal to find or insert if not found.
288 * @return Reference to mutable Mapped value directly inside the data structure.
289 */
290 Mapped& operator[](Key const & key);
291
292 /**
293 * Returns reference to mutable front ("newest") element in the structure; formally equivalent to
294 * `*(this->newest())`.
295 *
296 * OK to call when empty(); but behavior undefined if you attempt to access the result in any way if either empty()
297 * when this was called; or if `!empty()` at that time, but the underlying element is erased at time of access.
298 * If not empty() when this was called, then resulting reference continues to be valid as long as the underlying
299 * element is not erased; however, in the future the reference (while referring to the same element) might not refer
300 * to front ("newest") element any longer. (Informally, most uses would only call front() when `!empty()`, and would
301 * access it immediately and but once. However, I'm listing the corner cases above.)
302 *
303 * Note that if `Mapped& x` is returned, then although `x` is mutable, in actuality `x.first` is const; so only
304 * `x.second` is truly mutable. You must not write to the key (such as via a `const_cast<>`); doing so will result
305 * in undefined behavior.
306 *
307 * @return Reference to mutable value directly inside the data structure; or to undefined location if
308 * currently empty(). Note that only the `Mapped` part of `Value` is mutable.
309 */
311
312 /**
313 * Returns reference to mutable back ("oldest") element in the structure; formally equivalent to
314 * `*(--this->past_oldest())`.
315 *
316 * All other comments for front() apply analogously.
317 *
318 * @return Reference to mutable `Mapped` value directly inside the data structure; or to undefined location if
319 * currently empty().
320 */
322
323 /**
324 * Returns reference to immutable front ("newest") element in the structure; formally equivalent to
325 * `*(this->const_newest())`.
326 *
327 * OK to call when empty(); but behavior undefined if you attempt to access the result in any way if either empty()
328 * when this was called; or if `!empty()` at that time, but the underlying element is erased at time of access.
329 * If not empty() when this was called, then resulting reference continues to be valid as long as the underlying
330 * element is not erased; however, in the future the reference (while referring to the same element) may not refer
331 * to front ("newest") element any longer. (Informally, most uses would only call front() when `!empty()`, and would
332 * access it immediately and but once. However, I'm listing the corner cases above.)
333 *
334 * @return Reference to immutable `Mapped` value directly inside the data structure; or to undefined location if
335 * currently empty().
336 */
337 Value const & const_front() const;
338
339 /**
340 * Returns reference to immutable back ("oldest") element in the structure; formally equivalent
341 * to `*(--this->const_past_oldest())`.
342 *
343 * All other comments for const_front() apply analogously.
344 *
345 * @return Reference to immutable `Mapped` value directly inside the data structure; or to undefined location if
346 * currently empty().
347 */
348 Value const & const_back() const;
349
350 /**
351 * Given a valid iterator into the structure, makes the pointed to element "newest" by moving it from wherever it
352 * is to be first in the iteration order. Behavior undefined if iterator invalid.
353 *
354 * The iterator continues to be valid.
355 *
356 * @param it
357 * Iterator to an element of the structure.
358 */
359 void touch(Const_iterator const & it);
360
361 /**
362 * Given a key into the structure, makes the corresponding element "newest" by moving it from wherever it
363 * is to be first in the iteration order; or does nothing if no such key. Return value indicates various info of
364 * interest about what occurred or did not occur.
365 *
366 * @param key
367 * Key whose equal to find.
368 * @return `true` if the key was found (even if it was already "newest"); `false` if not found.
369 */
370 bool touch(Key const & key);
371
372 /**
373 * Erases the element pointed to by the given valid iterator. Behavior undefined if it is not valid. `it` becomes
374 * invalid.
375 *
376 * @param it
377 * Iterator of element to erase.
378 * @return Iterator one position past (i.e., "older") than `it`, before `*it` was removed.
379 */
381
382 /**
383 * Erases all elements in the range [`it_newest`, `it_past_oldest`). Behavior undefined if a given iterator is
384 * invalid. `it_newest` becomes invalid.
385 *
386 * @param it_newest
387 * Iterator of first ("newest") element to erase.
388 * @param it_past_oldest
389 * Iterator of one past last ("oldest") element to erase.
390 * @return `it_past_oldest` copy.
391 */
392 Iterator erase(Const_iterator const & it_newest, Const_iterator const & it_past_oldest);
393
394 /**
395 * Erases the element with the given key, if it exists. Return value indicates various info of interest about what
396 * occurred or did not occur.
397 *
398 * @param key
399 * Key such that its equal's (if found) element will be erased.
400 * @return Number of elements erased (0 or 1).
401 */
402 size_type erase(Key const & key);
403
404 /// Makes it so that `size() == 0`.
405 void clear();
406
407 /**
408 * Swaps the contents of this structure and `other`. This is a constant-time operation.
409 *
410 * @param other
411 * The other structure.
412 */
413 void swap(Linked_hash_map& other);
414
415 /**
416 * Synonym of newest().
417 * @return See newest().
418 */
420
421 /**
422 * Returns first, a/k/a "newest," element's iterator.
423 * @return Ditto.
424 */
426
427 /**
428 * Synonym of past_oldest(). Exists as standard container method.
429 * @return See past_oldest().
430 */
432
433 /**
434 * Returns one past last, a/k/a "oldest," element's iterator.
435 * @return Ditto.
436 */
438
439 /**
440 * Synonym of const_newest(). Exists as standard container method (hence the odd formatting).
441 * @return See const_newest().
442 */
444
445 /**
446 * Synonym of cbegin(). Exists to satisfy the C++11 rangy stuff (which makes `for(:)` -- and other magic -- work).
447 * @return See cbegin().
448 */
450
451 /**
452 * Returns first, a/k/a "newest," element's iterator (to immutable element).
453 * @return Ditto.
454 */
456
457 /**
458 * Synonym of const_past_oldest(). Exists as standard container method (hence the odd formatting).
459 * @return See const_past_oldest().
460 */
462
463 /**
464 * Synonym of cend(). Exists to satisfy the C++11 rangy stuff (which makes `for(:)` -- and other magic -- work).
465 * @return See cend().
466 */
468
469 /**
470 * Returns one past last, a/k/a "oldest," element's iterator (to immutable element).
471 * @return Ditto.
472 */
474
475 /**
476 * Synonym of oldest().
477 * @return See oldest().
478 */
480
481 /**
482 * Returns first, a/k/a "oldest," element's reverse iterator.
483 * @return Ditto.
484 */
486
487 /**
488 * Synonym of past_newest(). Exists as standard container method.
489 * @return See past_newest().
490 */
492
493 /**
494 * Returns one past last, a/k/a "newest," element's reverse iterator.
495 * @return Ditto.
496 */
498
499 /**
500 * Synonym of const_oldest().
501 * @return See const_oldest().
502 */
504
505 /**
506 * Returns first, a/k/a "oldest," element's reverse iterator (to immutable element).
507 * @return Ditto.
508 */
510
511 /**
512 * Synonym of const_past_newest(). Exists as standard container method.
513 * @return See const_past_newest().
514 */
516
517 /**
518 * Returns one past last, a/k/a "newest," element's reverse iterator (to immutable element).
519 * @return Ditto.
520 */
522
523 /**
524 * Returns true if and only if container is empty. Same performance as of `unordered_map<>`.
525 * @return Ditto.
526 */
527 bool empty() const;
528
529 /**
530 * Returns number of elements stored. Same performance as of `unordered_map<>.`
531 * @return Ditto.
532 */
534
535 /**
536 * Returns max number of elements that can be stored. Same performance as of `unordered_map<>` + `list<>`.
537 * @return Ditto.
538 */
540
541private:
542
543 // Methods.
544
545 /**
546 * Helper that modifies #m_value_list and #m_keys_into_list_map so that `key_and_mapped`'s copy is inserted into
547 * the structure. Pre-condition is that `key_and_mapped.first` is not in the structure (else behavior undefined).
548 *
549 * @param key_and_mapped
550 * Same as in insert().
551 * @return Same as in `insert().first`.
552 */
553 Iterator insert_impl(Value const & key_and_mapped);
554
555 // Types.
556
557 /// Short-hand for iterator into doubly linked list of (`Key`, `Mapped`) pairs.
559
560 /// Short-hand for `const` iterator into doubly linked list of (`Key`, `Mapped`) pairs.
562
563 /// Short-hand for a hash map that maps `Key` to iterator into doubly linked list of (`Key`, `Mapped`) pairs.
564 using Key_to_value_iter_map = boost::unordered_map<Key, Value_list_iter, Hash, Pred>;
565
566 // Data.
567
568 /**
569 * The actual values -- which, as in `unordered_map<K, M>`, are instances of `Value` = `pair<Key, Mapped>` --
570 * are stored in here, in the order in which user would iterate over them. If `Value v` is in this list, then no
571 * `Value v1 == v` can be elsewhere in the list. The order is semantically defined to be from "newest" to "oldest."
572 * Therefore, any newly inserted value goes at the START of the list. Similarly, any "touched" value is moved to
573 * the START of the list (see touch() and other methods that are documented as "touching" the referenced key).
574 * This ordering is what a normal `unordered_map<K, M>` would not supply (it's in the name!) but that we advertise.
575 *
576 * Since #m_keys_into_list_map stores keys, why store the keys here duplicately? Answer: that way we can expose
577 * iterators into #m_value_list directly to the user; so that they can take an iterator `I` and directly access
578 * the key and mapped value via `I->first` and `I->second`, respectively -- as is expected of any map container.
579 * This does, however, come at some memory cost.
580 *
581 * @todo It is probably possible to cut down on the memory cost of storing, for each element, a copy of the `Key`
582 * in #m_value_list (in addition to the mandatory one in the lookup table #m_keys_into_list_map). Perhaps the key
583 * copy would be replaced by an iterator back into #m_value_list. A custom iterator class would be necessary
584 * to properly dereference this (this is non-trivial given that `operator*()` would have to return a reference
585 * to a pair which is no longer stored anywhere in this hypothetical design). Moreover, iterators exposed to the
586 * user would become invalid the same way an `unordered_map<>` iterator does due to seemingly unrelated changes.
587 * Finally, the memory savings would not even exist for `Key` types roughly the size of a pointer. All in all,
588 * not a slam-dunk....
589 *
590 * ### Performance ###
591 * Moving a value from anywhere to either end of the list is a constant-time operation
592 * (assuming the source location's iterator is known). Hence touch() is constant-time. Moreover, touch()
593 * does NOT involve a copy of a `Value` (it only involves assigning, internally, a few linked list pointers).
594 * Also note that insertion is similarly constant-time (but does, necessarily, require a `Value` copy as for
595 * any container). Finally, erasure is also constant-time. These are the only operations needed.
596 */
598
599 /**
600 * Maps each `Key K` that is in #m_value_list to an iterator into #m_value_list (note the iterator points to
601 * a `Value` instance, which itself contains a copy of `K` but also the `Mapped` value, in which the user likely
602 * has keen interest). This supplies the one capability #m_value_list alone cannot: near-constant-time lookup
603 * of a `Value` or a `Mapped` by `Key` (a linear search would be necessary).
604 *
605 * The `unique_ptr<>` wrapper remains constant after setting it to non-null. Why have it at all? Because in at least
606 * one constructor we are unable to determine all the constructor arguments by the time the constructor body
607 * executes, and we don't want to construct the map until then.
608 *
609 * ### Performance ###
610 * Anything they'll need to do to this map carries the same performance cost as if they used a
611 * straight `unordered_map<>`, so by definition it is acceptable. The only operation this does not provide is
612 * iteration and insertion in the proper order, and that's done through #m_value_list instead.
613 */
614 boost::movelib::unique_ptr<Key_to_value_iter_map> m_keys_into_list_map;
615}; // class Linked_hash_map
616
617// Free functions: in *_fwd.hpp.
618
619// Template implementations.
620
621template<typename Key, typename Mapped, typename Hash, typename Pred>
623 hasher const & hasher_instance,
624 key_equal const & key_equal_instance) :
625 Linked_hash_map({}, n_buckets, hasher_instance, key_equal_instance)
626{
627 // Nothing.
628}
629
630template<typename Key, typename Mapped, typename Hash, typename Pred>
632 size_type n_buckets,
633 hasher const & hasher_instance,
634 key_equal const & key_equal_instance) :
635 // Their initializer_list is meant for a dictionary, but it is perfect for our list of pairs!
636 m_value_list(values)
637{
638 using boost::unordered_map;
639
640 /* Guess the default size, if they specified the default, from a dummy unrelated-type map. Probably
641 * that'll be correct. Even use our template argument values, just in case that matters. */
642 if (n_buckets == size_type(-1))
643 {
644 unordered_map<Key, Mapped, Hash, Pred> dummy;
645 n_buckets = dummy.bucket_count();
646 }
647
648 // We use a unique_ptr<> because of the above: we couldn't immediately initialize this map.
649 m_keys_into_list_map.reset(new Key_to_value_iter_map(n_buckets, hasher_instance, key_equal_instance));
650
651 // Now link each key in the quick-lookup table to its stored location in the ordering.
652 for (Value_list_iter value_list_it = m_value_list.begin(); value_list_it != m_value_list.end();
653 ++value_list_it)
654 {
655 // Note this sets (at key K) the value: iterator to pair<K, V>.
656 (*m_keys_into_list_map)[value_list_it->first] = value_list_it;
657 }
658}
659
660template<typename Key, typename Mapped, typename Hash, typename Pred>
662 m_keys_into_list_map(new Key_to_value_iter_map()) // Dummy: all this is quickly replaced.
663{
664 operator=(src);
665}
666
667template<typename Key, typename Mapped, typename Hash, typename Pred>
669 m_keys_into_list_map(new Key_to_value_iter_map()) // Dummy: all this is quickly replaced.
670{
671 operator=(std::move(src));
672}
673
674template<typename Key, typename Mapped, typename Hash, typename Pred>
677{
678 if (&src == this)
679 {
680 return *this;
681 }
682 // else
683
684 /* Values are values -- copy them over. Recall these are (const Key, Mapped) pairs.
685 *
686 * Why not just: `m_value_list = src.m_value_list;`? Answer: It fails to build, at least with this Darwin-supplied
687 * clang++, due to an interesting subtlety. Recall that the list stores (const Key, Mapped) pairs: note
688 * the `const`. Say you have iterator `it` into m_value_list; then `*it = <...>;` will not compile, as
689 * *it is partially const and cannot be assigned to (it is not "Assignable," in STL-speak). Yet the STL
690 * implementation in question will indeed perform that operation. But why? To make the new m_value_list,
691 * just create node after node, each one storing a copy of the source node's corresponding (const Key, Mapped)
692 * (a/k/a Value!) pair, right? That would only require that Value (and therefore `const Key`, which is
693 * half of it) be CopyConstructible (again, in STL-speak, which basically matches English here). Yes, but this
694 * implementation, for performance, instead keeps m_value_list's existing node structure intact, only *assigning*
695 * over (via operator=(), not via copy constructor!) the stored value inside each node; only "resorting" to
696 * making new nodes and copy-constructing things once it has run out of m_value_list's own nodes. Clever but
697 * messes us over.
698 *
699 * Note that this can't be worked around by merely performing an m_value_list.clear() before the list assignment;
700 * the code is still the code (even if it would not run in actuality due to the pre-clearing) and wouldn't
701 * compile. However, by writing it as an insert(), it will absolutely force it to make a bunch of copies of the
702 * nodes, ensuring it compiles and works fine:
703 * m_value_list.clear();
704 * m_value_list.insert(m_value_list.end(), src.m_value_list.begin(), src.m_value_list.end());
705 *
706 * That's fine. However, it does actually bypass a nice performance trick! We would rather not make that
707 * concession. Therefore, let's temporarily pretend m_value_list and src.m_value_list store non-const Keys.
708 * Then we can assing. Note that, even though it's N lines of code, reinterpret_cast<> generates no machine
709 * code: it just makes the code-that-would-have-been-generated-anyway look at the memory in a different way
710 * (in this case, as storing Keys that can be overwritten instead of read-only ones). Finally, note that
711 * we specifically require that the template parameter `typename Key` be Assignable; that is the piece of the
712 * puzzle that GUARANTEES this reinterpret_cast<> (in general not a safe operation) is indeed safe/correct. */
713 {
714 using std::pair;
715 using std::list;
716
717 using Mutable_key_value = pair<Key, Mapped>;
718 using Mutable_key_value_list = list<Mutable_key_value>;
719
720 Mutable_key_value_list* const dst_list_ptr
721 = reinterpret_cast<Mutable_key_value_list*>(&m_value_list);
722 const Mutable_key_value_list* const src_list_ptr
723 = reinterpret_cast<const Mutable_key_value_list*>(&src.m_value_list);
724
725 *dst_list_ptr = *src_list_ptr;
726 }
727
728 /* However, the iterators in any hash map would point to the wrong list! Build up that map from scratch.
729 *
730 * ...Actually, not quite. To attempt to keep the same structure as the source map, first copy it; then
731 * overwrite the values. Not sure how perfectly it works but seems worth a shot, as a regular unordered_map<>
732 * promises to copy over things like the load factor of the source object, not to mention the hasher and
733 * equality predicate, and we advertise the same. */
734 *m_keys_into_list_map = *src.m_keys_into_list_map;
735
736 // So now replace the keys in the ready map.
737 for (Value_list_iter value_list_it = m_value_list.begin(); value_list_it != m_value_list.end();
738 ++value_list_it)
739 {
740 // Note this sets (at key K) the value: iterator to pair<K, V>.
741 (*m_keys_into_list_map)[value_list_it->first] = value_list_it;
742 }
743
744 return *this;
745} // Linked_hash_map::operator=()
746
747template<typename Key, typename Mapped, typename Hash, typename Pred>
750{
751 if (&src != this)
752 {
753 clear();
754 swap(std::move(src));
755 }
756 return *this;
757}
758
759template<typename Key, typename Mapped, typename Hash, typename Pred>
760std::pair<typename Linked_hash_map<Key, Mapped, Hash, Pred>::Iterator, bool>
762{
763 using std::pair;
764
765 Key const & key = key_and_mapped.first;
766
767 // Check if this key is already in the map of iterators and therefore overall map.
768 typename Key_to_value_iter_map::iterator const map_it = m_keys_into_list_map->find(key);
769 if (map_it != m_keys_into_list_map->end())
770 {
771 // Yes. *map_it is pair<Key, Iterator>. So return 2nd half of that as the iterator to already existing element!
772 return pair<Iterator, bool>(map_it->second, false);
773 }
774 // else Nope.
775
776 return pair<Iterator, bool>(insert_impl(key_and_mapped), true);
777}
778
779template<typename Key, typename Mapped, typename Hash, typename Pred>
782{
783 /* Insert it at the front: as advertised, new element is "touched," meaning it is made "newest," so goes at start.
784 * Note that "it" = a copy of key_and_mapped. */
785 m_value_list.push_front(key_and_mapped);
786
787 // Iterator to the new element is therefore iterator to start of list of <Key, Mapped> pairs.
788 Iterator const new_elem_it = m_value_list.begin();
789 // And make sure we can look it up in the future quickly (such as what is done above).
790 (*m_keys_into_list_map)[key_and_mapped.first] = new_elem_it;
791
792 return new_elem_it;
793}
794
795template<typename Key, typename Mapped, typename Hash, typename Pred>
798{
799 typename Key_to_value_iter_map::iterator const map_it = m_keys_into_list_map->find(key);
800 return (map_it == m_keys_into_list_map->end()) ? m_value_list.end() : map_it->second;
801}
802
803template<typename Key, typename Mapped, typename Hash, typename Pred>
806{
807 typename Key_to_value_iter_map::const_iterator const map_it = m_keys_into_list_map->find(key);
808 return (map_it == m_keys_into_list_map->cend()) ? m_value_list.cend() : map_it->second;
809}
810
811template<typename Key, typename Mapped, typename Hash, typename Pred>
814{
815 return m_keys_into_list_map->count(key);
816}
817
818template<typename Key, typename Mapped, typename Hash, typename Pred>
820{
821 using std::pair;
822
823 // Check if this key is already in the map of iterators and therefore overall map.
824 typename Key_to_value_iter_map::iterator const map_it = m_keys_into_list_map->find(key);
825 if (map_it != m_keys_into_list_map->end())
826 {
827 // Yes. *map_it is pair<Key, Iterator>. *(that Iterator) is pair<Key, Value>. Return 2nd half of latter.
828 return map_it->second->second;
829 }
830 // else Nope.
831
832 return insert_impl(Value(key, Mapped()))->second;
833}
834
835template<typename Key, typename Mapped, typename Hash, typename Pred>
838{
839 // No assert(): we promised not to crash even if empty(). They just can't access it subsequently if so.
840 return *(newest());
841}
842
843template<typename Key, typename Mapped, typename Hash, typename Pred>
846{
847 // No assert(): we promised not to crash even if empty(). They just can't access it subsequently if so.
848 return *(--past_oldest());
849}
850
851template<typename Key, typename Mapped, typename Hash, typename Pred>
854{
855 // No assert(): we promised not to crash even if empty(). They just can't access it subsequently if so.
856 return *(const_newest());
857}
858
859template<typename Key, typename Mapped, typename Hash, typename Pred>
862{
863 // No assert(): we promised not to crash even if empty(). They just can't access it subsequently if so.
864 return *(--const_past_oldest());
865}
866
867template<typename Key, typename Mapped, typename Hash, typename Pred>
869{
870 m_value_list.splice(m_value_list.begin(), m_value_list, it);
871}
872
873template<typename Key, typename Mapped, typename Hash, typename Pred>
875{
876 const Const_iterator it = find(key);
877 if (it == end())
878 {
879 return false;
880 }
881 // else
882
883 touch(it);
884 return true;
885}
886
887template<typename Key, typename Mapped, typename Hash, typename Pred>
890{
891 m_keys_into_list_map->erase(m_keys_into_list_map->find(it->first));
892 return m_value_list.erase(it);
893}
894
895template<typename Key, typename Mapped, typename Hash, typename Pred>
898 Const_iterator const & it_past_oldest)
899{
900 for (Value_list_const_iter it = it_newest; it != it_past_oldest; ++it)
901 {
902 m_keys_into_list_map->erase(it->first);
903 }
904
905 return m_value_list.erase(it_newest, it_past_oldest);
906}
907
908template<typename Key, typename Mapped, typename Hash, typename Pred>
911{
912 typename Key_to_value_iter_map::iterator const map_it = m_keys_into_list_map->find(key);
913 if (map_it == m_keys_into_list_map->end())
914 {
915 return 0;
916 }
917 // else
918
919 m_value_list.erase(map_it->second);
920 m_keys_into_list_map->erase(map_it);
921
922 return 1;
923}
924
925template<typename Key, typename Mapped, typename Hash, typename Pred>
927{
928 m_keys_into_list_map->clear();
929 m_value_list.clear();
930}
931
932template<typename Key, typename Mapped, typename Hash, typename Pred>
934{
935 using std::swap;
936
937 swap(m_keys_into_list_map, other.m_keys_into_list_map); // unique_ptr<>s exchanged (= raw pointers exchanged).
938 swap(m_value_list, other.m_value_list); // list<> exchange (probably = head+tail pointer pairs exchanged).
939 // Per cppreference.com `list<>::iterator`s (inside the `_maps`s) remain valid after list<>s swapped.
940}
941
942template<typename Key, typename Mapped, typename Hash, typename Pred>
945{
946 return m_value_list.begin();
947}
948
949template<typename Key, typename Mapped, typename Hash, typename Pred>
952{
953 return newest();
954}
955
956template<typename Key, typename Mapped, typename Hash, typename Pred>
959{
960 return m_value_list.end();
961}
962
963template<typename Key, typename Mapped, typename Hash, typename Pred>
966{
967 return past_oldest();
968}
969
970template<typename Key, typename Mapped, typename Hash, typename Pred>
973{
974 return m_value_list.cbegin();
975}
976
977template<typename Key, typename Mapped, typename Hash, typename Pred>
980{
981 return const_newest();
982}
983
984template<typename Key, typename Mapped, typename Hash, typename Pred>
987{
988 return const_newest();
989}
990
991template<typename Key, typename Mapped, typename Hash, typename Pred>
994{
995 return m_value_list.cend();
996}
997
998template<typename Key, typename Mapped, typename Hash, typename Pred>
1001{
1002 return const_past_oldest();
1003}
1004
1005template<typename Key, typename Mapped, typename Hash, typename Pred>
1008{
1009 return const_past_oldest();
1010}
1011
1012template<typename Key, typename Mapped, typename Hash, typename Pred>
1015{
1016 return m_value_list.rbegin();
1017}
1018
1019template<typename Key, typename Mapped, typename Hash, typename Pred>
1022{
1023 return oldest();
1024}
1025
1026template<typename Key, typename Mapped, typename Hash, typename Pred>
1029{
1030 return m_value_list.rend();
1031}
1032
1033template<typename Key, typename Mapped, typename Hash, typename Pred>
1036{
1037 return past_newest();
1038}
1039
1040template<typename Key, typename Mapped, typename Hash, typename Pred>
1043{
1044 return m_value_list.crbegin();
1045}
1046
1047template<typename Key, typename Mapped, typename Hash, typename Pred>
1050{
1051 return const_oldest();
1052}
1053
1054template<typename Key, typename Mapped, typename Hash, typename Pred>
1057{
1058 return m_value_list.crend();
1059}
1060
1061template<typename Key, typename Mapped, typename Hash, typename Pred>
1064{
1065 return const_past_newest();
1066}
1067
1068template<typename Key, typename Mapped, typename Hash, typename Pred>
1071{
1072 return m_keys_into_list_map->size(); // I'm skeptical/terrified of list::size()'s time complexity.
1073}
1074
1075template<typename Key, typename Mapped, typename Hash, typename Pred>
1077{
1078 return m_keys_into_list_map->empty();
1079}
1080
1081template<typename Key, typename Mapped, typename Hash, typename Pred>
1084{
1085 return std::min(m_keys_into_list_map->max_size(), m_value_list.max_size());
1086}
1087
1088template<typename Key, typename Mapped, typename Hash, typename Pred>
1090{
1091 val1.swap(val2);
1092}
1093
1094} // namespace flow::util
An object of this class is a map that combines the lookup speed of a boost::unordered_map<> and order...
boost::movelib::unique_ptr< Key_to_value_iter_map > m_keys_into_list_map
Maps each Key K that is in m_value_list to an iterator into m_value_list (note the iterator points to...
std::ptrdiff_t difference_type
Type for difference of size_types.
Const_iterator end() const
Synonym of cend().
Iterator erase(Const_iterator const &it)
Erases the element pointed to by the given valid iterator.
typename Value_list::const_reverse_iterator Const_reverse_iterator
Type for reverse iterator pointing into an immutable structure of this type.
Value & front()
Returns reference to mutable front ("newest") element in the structure; formally equivalent to *(this...
typename Value_list::reverse_iterator Reverse_iterator
Type for reverse iterator pointing into a mutable structure of this type.
size_type count(Key const &key) const
Returns the number of times a key is equivalent to the given one is present in the map: either 1 or 0...
Iterator iterator
For container compliance (hence the irregular capitalization): Iterator type.
typename Value_list::const_iterator Const_iterator
Type for iterator pointing into an immutable structure of this type.
Iterator Value_list_iter
Short-hand for iterator into doubly linked list of (Key, Mapped) pairs.
size_type max_size() const
Returns max number of elements that can be stored.
Const_iterator const_iterator
For container compliance (hence the irregular capitalization): Const_iterator type.
Linked_hash_map(Linked_hash_map const &src)
Constructs object that is a copy of the given source.
typename Value_list::iterator Iterator
Type for iterator pointing into a mutable structure of this type.
size_type erase(Key const &key)
Erases the element with the given key, if it exists.
Value_list m_value_list
The actual values – which, as in unordered_map<K, M>, are instances of Value = pair<Key,...
Const_iterator cbegin() const
Synonym of const_newest().
Const_iterator cend() const
Synonym of const_past_oldest().
Iterator newest()
Returns first, a/k/a "newest," element's iterator.
Const_iterator const_past_oldest() const
Returns one past last, a/k/a "oldest," element's iterator (to immutable element).
Mapped & operator[](Key const &key)
Equivalent to insert(Value(key, Mapped())).first->second (but avoids unnecessarily invoking Mapped()/...
Value const * const_pointer
For container compliance (hence the irregular capitalization): pointer to const Key/Mapped pair type.
Pred key_equal
For container compliance (hence the irregular capitalization): Pred type.
Value & back()
Returns reference to mutable back ("oldest") element in the structure; formally equivalent to *(--thi...
Reverse_iterator rbegin()
Synonym of oldest().
bool touch(Key const &key)
Given a key into the structure, makes the corresponding element "newest" by moving it from wherever i...
Value & reference
For container compliance (hence the irregular capitalization): reference to Key/Mapped pair type.
Value const & const_front() const
Returns reference to immutable front ("newest") element in the structure; formally equivalent to *(th...
Const_iterator const_newest() const
Returns first, a/k/a "newest," element's iterator (to immutable element).
Reverse_iterator rend()
Synonym of past_newest().
Value const & const_reference
For container compliance (hence the irregular capitalization): reference to const Key/Mapped pair typ...
Iterator end()
Synonym of past_oldest().
Reverse_iterator past_newest()
Returns one past last, a/k/a "newest," element's reverse iterator.
void touch(Const_iterator const &it)
Given a valid iterator into the structure, makes the pointed to element "newest" by moving it from wh...
void swap(Linked_hash_map &other)
Swaps the contents of this structure and other.
Value * pointer
For container compliance (hence the irregular capitalization): pointer to Key/Mapped pair type.
Const_reverse_iterator crend() const
Synonym of const_past_newest().
Linked_hash_map(std::initializer_list< Value > values, size_type n_buckets=size_type(-1), Hash const &hasher_instance=Hash(), Pred const &key_equal_instance=Pred())
Constructs structure with some basic parameters, and values initialized from initializer list.
Linked_hash_map & operator=(Linked_hash_map &&src)
Overwrites this object making it equal to the given source, while the given source becomes as-if defa...
Iterator find(Key const &key)
Attempts to find value at the given key in the map.
std::pair< Iterator, bool > insert(Value const &key_and_mapped)
Attempts to insert the given key/mapped-value pair into the map.
Iterator past_oldest()
Returns one past last, a/k/a "oldest," element's iterator.
std::size_t size_type
Type for index into array of items, where items are all applicable objects including Values and Keys.
Linked_hash_map(size_type n_buckets=size_type(-1), Hash const &hasher_instance=Hash(), Pred const &key_equal_instance=Pred())
Constructs empty structure with some basic parameters.
size_type size() const
Returns number of elements stored.
Const_iterator find(Key const &key) const
Attempts to find value at the given key in the map.
Iterator begin()
Synonym of newest().
Linked_hash_map(Linked_hash_map &&src)
Constructs object by making it equal to the given source, while the given source becomes as-if defaul...
Key key_type
For container compliance (hence the irregular capitalization): Key type.
boost::unordered_map< Key, Value_list_iter, Hash, Pred > Key_to_value_iter_map
Short-hand for a hash map that maps Key to iterator into doubly linked list of (Key,...
Linked_hash_map & operator=(Linked_hash_map const &src)
Overwrites this object with a copy of the given source.
Iterator insert_impl(Value const &key_and_mapped)
Helper that modifies m_value_list and m_keys_into_list_map so that key_and_mapped's copy is inserted ...
void clear()
Makes it so that size() == 0.
Mapped mapped_type
For container compliance (hence the irregular capitalization): Mapped type.
Const_iterator Value_list_const_iter
Short-hand for const iterator into doubly linked list of (Key, Mapped) pairs.
std::list< Value > Value_list
Short-hand for doubly linked list of (Key, Mapped) pairs.
Hash hasher
For container compliance (hence the irregular capitalization): Hash type.
Const_reverse_iterator const_oldest() const
Returns first, a/k/a "oldest," element's reverse iterator (to immutable element).
Const_reverse_iterator const_past_newest() const
Returns one past last, a/k/a "newest," element's reverse iterator (to immutable element).
Const_iterator begin() const
Synonym of cbegin().
Const_reverse_iterator crbegin() const
Synonym of const_oldest().
Value const & const_back() const
Returns reference to immutable back ("oldest") element in the structure; formally equivalent to *(--t...
Value value_type
For container compliance (hence the irregular capitalization): Key/Mapped pair type.
Reverse_iterator oldest()
Returns first, a/k/a "oldest," element's reverse iterator.
Iterator erase(Const_iterator const &it_newest, Const_iterator const &it_past_oldest)
Erases all elements in the range [it_newest, it_past_oldest).
std::pair< Key const, Mapped > Value
Short-hand for key/mapped-value pairs stored in the structure.
bool empty() const
Returns true if and only if container is empty.
Flow module containing miscellaneous general-use facilities that don't fit into any other Flow module...
Definition: basic_blob.hpp:29
void swap(Basic_blob< Allocator, S_SHARING_ALLOWED > &blob1, Basic_blob< Allocator, S_SHARING_ALLOWED > &blob2, log::Logger *logger_ptr)
Equivalent to blob1.swap(blob2).
void swap(Linked_hash_map< Key, Mapped, Hash, Pred > &val1, Linked_hash_map< Key, Mapped, Hash, Pred > &val2)
Equivalent to val1.swap(val2).