Flow 2.0.0
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
23#include <cstddef>
24
25namespace flow::util
26{
27
28/**
29 * An object of this class is a map that combines the lookup speed of an `unordered_map<>` and ordering and
30 * iterator stability capabilities of a `list<>`.
31 *
32 * The API is generally that of an `unordered_map<>`. The differences essentially all have to do with iterators.
33 * This map introduces a concept of "newness," which determines the iteration order. Moreover, *every* iterator remains
34 * valid except (of course) under erasure of the underlying element. Newness is defined as follows inductively:
35 * whenever an element is inserted, it is "newest," thus it is placed at the front of the iterator order. Furthermore,
36 * the methods touch() can be used to make any element "newest" (moved to the front of the iterator order). Iterator
37 * thus formed orders elements from newest to oldest (hence newest() is begin(), past_oldest() is end()).
38 *
39 * Performance expectations: The best way to determine a method's time needs is to
40 * imagine what it must do. If it must perform a lookup by key, that is an `unordered_set<>` lookup resulting in an
41 * (at least internal) iterator. If it must insert an element, it is always inserted at the start of a `list`; and
42 * also into an `unordered_set<>`. If it must erase an element based on an iterator, that element is erased from a list
43 * based on that iterator; and also by key from said `unordered_set<>`. Iteration itself is iteration along a `list`.
44 * But essentially, every operation is either near constant time or constant time.
45 * In terms of space needs, this essentially stores the values themselves in a `list`; and also a pointer to each
46 * list-held key/element in an `unordered_set<>`, which also stores a pointer or list iterator per element.
47 *
48 * Move semantics for both keys and mapped-values are supported (let `T` be a concrete type for a `*this` and `x`
49 * a `*this`):
50 * - `x.insert(std::make_pair<Key, Mapped>(..., ...))`;
51 * - or `x.insert(T::Value_movable{..., ...})`;
52 * - `x[std::move(...)] = std::move(...)`.
53 *
54 * There is the standard complement of container-wide move operations: move-construction, move-assignment, and
55 * `swap()` (all constant-time, excluding any implied `this->clear()` in the move-assignment).
56 *
57 * The iterators are, really, `list<pair<const Key, Mapped>>` iterators; and as such are not invalidated except
58 * due to direct erasure of a given pointee.
59 *
60 * @todo Linked_hash_map and Linked_hash_set have a reasonable complement of C++1x-ish APIs including move-semantics;
61 * but the API does not quite mirror the full complement of what is in existence for `unordered_*` counterparts in
62 * C++17 STL/Boost -- it would be nice to add these. This includes such things as `.emplace()` and `.try_emplace()`
63 * but more fundamentally would probably involve trolling `std::unordered_*` and copying its ~full API (and likely
64 * some of a decent impl too). That said what's available already acquits itself reasonably well. (Historically
65 * this was first written before C++11 and hasn't been given the full-on C++1x overhaul but instead merely the
66 * essentials thereof.)
67 *
68 * ### Thread safety ###
69 * Same as for `unordered_map<>`.
70 *
71 * @internal
72 * ### Impl notes ###
73 * You should get much of what you need to grok this just by reading the above and possibly looking at the Data
74 * section doc-headers under `private:`. Essentially, to repeat/recap: there's the `list<pair<const Key, Mapped>>`
75 * to store the actual values, in order (#m_value_list); #Iterator and #Const_iterator come directly from there.
76 *
77 * When lookup by #Key is needed, the `unordered_set` #m_value_iter_set comes into play. This is arguably the
78 * only real mechanical trickiness. It actually stores `Iterator`s into #m_value_list but in such a way as to allow
79 * seamless lookup from a mere `const Key&`; so `m_value_iter_set.find(key)` "magically" either finds `.end()` --
80 * then the key is not in `*this` -- or the iterator into the actual key/mapped-value store in #m_value_list.
81 * Using #m_value_iter_set is easy; but a bit of internal infrastructure is necessary to have it work. Namely
82 * we have support class template `Linked_hash_key<Key, Iterator>`, and the `unordered_set m_value_iter_set`
83 * actually stores those guys, not raw #Key copies; so it is a wrapper around an `Iterator` *or* a #Key
84 * (union-style). #m_value_iter_set stores #Iterator wrappers, while lookup attempts use the #Key wrapper form
85 * to pass into `m_value_iter_set.find()`.
86 *
87 * An earlier version of Linked_hash_map instead used a simple `unordered_map<Key, Iterator>` instead of the
88 * `unordered_set<Linked_hash_key<Key, Iterator>>`; so a lookup by key was just that.
89 * Eventually we replaced it with the more complex solution simply to avoid storing 2 copies of
90 * each #Key (one in the list, one in the map); as the `Iterator` itself
91 * includes a pointer to the thing containing the corresponding #Key in the first place. So this saves memory
92 * as well as various #Key copying.
93 *
94 * @endinternal
95 *
96 * @tparam Key_t
97 * Key type. We omit formal requirements, as it is tedious and full of corner cases depending on what
98 * you plan to invoke (e.g., whether you use move-semantics for keys). Please use common sense knowing
99 * the basic data structures involved as explained above. That said: if #Key is of non-trivial size,
100 * it is good to have it have performant move-constructibility and move-assignability and then make use
101 * of it via move-aware APIs as suggested in the doc header above.
102 * @tparam Mapped_t
103 * The 2nd (satellite) part of the #Value pair type. Same commentary as for #Key applies here.
104 * @tparam Hash_t
105 * Hasher type. Same requirements and behavior as `boost::unordered_set<>` counterpart. If using
106 * the default value for #Hash (`boost::hash<Key>`), and the default object is passed to ctor (`Hash{}`) (this
107 * is typical), but there is no hash-function already defined for #Key, then the easiest way to define
108 * it is: make a `size_t hash_value(Key)` free function in the same namespace as #Key.
109 * @tparam Pred_t
110 * Equality-determiner type. Same requirements and behavior as `boost::unordered_set<>` counterpart. If using
111 * the default value for #Pred (`std::equal_to<Key>`), and the default object is passed to ctor (`Pred{}`)
112 * (this is typical), but there is no equality op defined for #Key, then the easiest way to define
113 * it is: make an operator-method or free function such that `k1 == k2` (where `k1` and `k2` are `Key`s)
114 * determines equality or lack thereof.
115 */
116template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
118{
119public:
120 // Types.
121
122 /// Convenience alias for template arg.
123 using Key = Key_t;
124
125 /// Convenience alias for template arg.
126 using Mapped = Mapped_t;
127
128 /// Convenience alias for template arg.
129 using Hash = Hash_t;
130
131 /// Convenience alias for template arg.
132 using Pred = Pred_t;
133
134 /// Short-hand for key/mapped-value pairs stored in the structure.
135 using Value = std::pair<Key const, Mapped>;
136
137 /// Short-hand for key/mapped-value pair best-suited (perf-wise) as arg type for the moving `insert()` overload.
138 using Value_movable = std::pair<Key, Mapped>;
139
140private:
141 // Types. These are here in the middle of public block due to inability to forward-declare aliases.
142
143 /// Short-hand for doubly linked list of (#Key, #Mapped) pairs.
144 using Value_list = std::list<Value>;
145
146public:
147
148 // Types (continued).
149
150 /// Expresses sizes/lengths of relevant things.
151 using size_type = std::size_t;
152 /// Type for difference of `size_type`s.
153 using difference_type = std::ptrdiff_t;
154
155 /// Type for iterator pointing into a mutable structure of this type.
156 using Iterator = typename Value_list::iterator;
157
158 /// Type for iterator pointing into an immutable structure of this type.
159 using Const_iterator = typename Value_list::const_iterator;
160
161 /// Type for reverse iterator pointing into a mutable structure of this type.
162 using Reverse_iterator = typename Value_list::reverse_iterator;
163
164 /// Type for reverse iterator pointing into an immutable structure of this type.
165 using Const_reverse_iterator = typename Value_list::const_reverse_iterator;
166
167 /// For container compliance (hence the irregular capitalization): #Key type.
168 using key_type = Key;
169 /// For container compliance (hence the irregular capitalization): #Mapped type.
171 /// For container compliance (hence the irregular capitalization): #Key/#Mapped pair type.
173 /// For container compliance (hence the irregular capitalization): #Hash type.
174 using hasher = Hash;
175 /// For container compliance (hence the irregular capitalization): #Pred type.
177 /// For container compliance (hence the irregular capitalization): pointer to #Key/#Mapped pair type.
178 using pointer = Value*;
179 /// For container compliance (hence the irregular capitalization): pointer to `const Key`/#Mapped pair type.
180 using const_pointer = const Value*;
181 /// For container compliance (hence the irregular capitalization): reference to #Key/#Mapped pair type.
182 using reference = Value&;
183 /// For container compliance (hence the irregular capitalization): reference to `const Key`/#Mapped pair type.
184 using const_reference = const Value&;
185 /// For container compliance (hence the irregular capitalization): #Iterator type.
187 /// For container compliance (hence the irregular capitalization): #Const_iterator type.
189
190 // Constructors/destructor.
191
192 /**
193 * Constructs empty structure with some basic parameters.
194 *
195 * @param n_buckets
196 * Number of buckets for the unordered (hash) table. Special value -1 (default) will cause us to use
197 * whatever `unordered_set<>` would use by default.
198 * @param hasher_obj
199 * Instance of the hash function type (`hasher_obj(k) -> size_t` should be hash of `Key k`).
200 * @param pred
201 * Instance of the equality function type (`pred(k1, k2)` should return `true` if and
202 * only if the `Key`s are equal by value).
203 */
205 const Hash& hasher_obj = Hash{},
206 const Pred& pred = Pred{});
207
208 /**
209 * Constructs structure with some basic parameters, and values initialized from initializer list.
210 * The values are inserted as if `insert(v)` was called for each pair `v` in `values`
211 * **in reverse order**. Since the canonical ordering places the *newest* (last inserted/`touch()`ed)
212 * element at the *front* of the ordering, that means that forward iteration through the set (right after this
213 * constructor runs) will yield values in the *same* order as in initializer list `values`.
214 *
215 * @param values
216 * Values with which to fill the structure after initializing it.
217 * Typically you'd provide a series of key/value pairs like this:
218 * `{ { key1, value1 }, { key2, value2 }, ... }`. They will appear in iterated sequence in the same order as
219 * they appear in this list.
220 * @param n_buckets
221 * See other constructor.
222 * @param hasher_obj
223 * See other constructor.
224 * @param pred
225 * See other constructor.
226 */
227 explicit Linked_hash_map(std::initializer_list<Value> values,
228 size_type n_buckets = size_type(-1),
229 const Hash& hasher_obj = Hash{},
230 const Pred& pred = Pred{});
231
232 /**
233 * Constructs object that is a copy of the given source. Equivalent to default-ction followed by `operator=(src)`.
234 *
235 * @param src
236 * Source object.
237 */
239
240 /**
241 * Constructs object by making it equal to the given source, while the given source becomes as-if default-cted.
242 * Equivalent to default-ction followed by `operator=(std::move(src))`.
243 *
244 * This is a constant-time operation.
245 *
246 * @param src
247 * Source object which is emptied.
248 */
250
251 // Methods.
252
253 /**
254 * Overwrites this object with a copy of the given source. We become equal to `src` but independent of it to a
255 * common-sense extent. In addition, the hasher instance and equality predicate are copied from `src`. Finally, a
256 * reasonable attempt is made to also make the internal structure of the hash map to be similar to that of `src.
257 *
258 * @param src
259 * Source object. No-op if `this == &src`.
260 * @return `*this`.
261 */
263
264 /**
265 * Overwrites this object making it identical to the given source, while the given source becomes as-if default-cted.
266 *
267 * This is a constant-time operation, plus whatever is the cost of `this->clear()` (linear in pre-op `.size()`).
268 *
269 * @param src
270 * Source object which is emptied; except no-op if `this == &src`.
271 * @return `*this`.
272 */
274
275 /**
276 * Swaps the contents of this structure and `other`. This is a constant-time operation, as internal
277 * representations are swapped instead of any copy-assignment.
278 *
279 * @see The `swap()` free function.
280 * It is generally best (equivalent but covers more generic cases) to use the ADL-enabled `swap(a, b)`
281 * pattern instead of this member function. That is: `using std::swap; ...; swap(a, b);`.
282 * (Details are outside our scope here; but in short ADL will cause the right thing to happen.)
283 *
284 * @param other
285 * The other structure.
286 */
287 void swap(Linked_hash_map& other);
288
289 /**
290 * Attempts to insert (copying both key and mapped-value) the given key/mapped-value pair into the map; if key
291 * already in `*this` makes no change. See also the overload which can avoid a copy and destructively move
292 * the key and mapped-value instead.
293 *
294 * Return value indicates various info of interest about what occurred or did not occur.
295 * If inserted, the new element is considered "newest," as if by touch(). If not inserted, the existing element
296 * location is not affected (use touch() upon consulting the return value, if this is desirable).
297 *
298 * @param key_and_mapped
299 * The key/mapped-value pair to attempt to insert. A copy of this value is placed in `*this`.
300 * @return A pair whose second element is true if and only if the insertion occurred; and whose first element
301 * is an iterator pointing to either the newly inserted element or already present one with a key equal to
302 * `key_and_mapped.first`.
303 */
304 std::pair<Iterator, bool> insert(const Value& key_and_mapped);
305
306 /**
307 * Identical to the other overload, except that (if key not already present in `*this`) the key and mapped-value
308 * are moved, not copied, into `*this`.
309 *
310 * @note `key_and_mapped` pointee must be of type #Value_movable, a/k/a `pair<Key, Mapped>` -- not
311 * #Value, a/k/a `pair<const Key, Mapped>` -- otherwise the other insert() overload may get invoked,
312 * and copying may occur contrary to your intention. E.g., use `std::make_pair<Key, Mapped>()` or
313 * `"decltype(*this)::Value_movable{}"`.
314 * (For a move to occur, the source-object can't be `const`; so that's why.)
315 * @note You can often also use `x[std::move(key)] = std::move(value)`, particularly if you know `key` isn't in
316 * there, or you are OK with replacing the value if it is. In those cases it's probably more convenient,
317 * no pairs or `Value_movable`s to worry oneself.
318 *
319 * @param key_and_mapped
320 * The key/mapped-value pair to attempt to insert (both key and mapped-value are moved-from, if insertion
321 * occurs).
322 * @return See other overload.
323 */
324 std::pair<Iterator, bool> insert(Value_movable&& key_and_mapped);
325
326 /**
327 * Either finds the #Mapped value at the given key, or if not found inserts one with a default-constructed
328 * `Mapped{}`; then returns reference to the #Mapped. That ref can be used to read and/or modify that value
329 * directly. See also the overload which can avoid a copy and destructively move the key instead.
330 *
331 * If inserted, the new element is considered "newest," as if by touch(). If not inserted, the existing element
332 * location is not affected.
333 *
334 * So it is ~equivalent to
335 *
336 * - (`key` is in map) `return this->find(key)->second`; or
337 * - (otherwise) `return this->insert(key, Mapped{}).first->second`.
338 *
339 * As long as the value is not removed from the map, the reference will continue to be valid.
340 *
341 * Any subsequent writes to the referred-to area of memory will NOT have the effect of touch(). If you need it
342 * call touch() yourself.
343 *
344 * @param key
345 * Key whose equal to find or insert if not found. A copy of this value is placed in `*this`.
346 * @return Reference to mutable #Mapped value directly inside the data structure.
347 */
348 Mapped& operator[](const Key& key);
349
350 /**
351 * Identical to the other overload, except that (if key not already present in `*this`) the key
352 * is moved, not copied, into `*this`.
353 *
354 * @param key
355 * The key to attempt to insert (key is moved-from, if insertion occurs).
356 * @return See other overload.
357 */
359
360 /**
361 * Attempts to find value at the given key in the map. Key presence is determined identically to how it would be
362 * done in an `unordered_set<Key, Hash, Pred>`, with the particular #Hash and #Pred instances given to ctor
363 * (typically their default-cted instances, typically occupying no memory).
364 *
365 * The returned iterator (if valid) can be used to mutate the element inside the map; though only the #Mapped
366 * is mutable; the `const Key` is immutable.
368 * Any subsequent writes to the referred-to (by returned iterator) area of memory will NOT have the effect of touch().
369 * If you need it call touch() yourself.
370 *
371 * @param key
372 * Key whose equal to find.
373 * @return If found, iterator to the key/mapped-value pair with the equivalent key; else `this->end()`.
374 */
375 Iterator find(const Key& key);
376
377 /**
378 * Identical to the other overload but in a `const` context: the returned iterator is to immutable memory.
379 *
380 * @param key
381 * Key whose equal to find.
382 * @return If found, iterator to the key/mapped-value pair with the equivalent key; else `this->cend()`.
383 */
384 Const_iterator find(const Key& key) const;
385
386 /**
387 * Returns the number of times a key equal to the given one is present (as-if via find()) in the map: either 1 or 0.
388 *
389 * @param key
390 * Key whose equal to find.
391 * @return 0 or 1.
392 */
393 size_type count(const Key& key) const;
394
395 /**
396 * Given a valid iterator into the structure, makes the pointed-to element "newest" by moving it from wherever it
397 * is to be first in the iteration order. Behavior undefined if iterator invalid.
398 *
399 * The iterator continues to be valid.
400 *
401 * @param it
402 * Iterator to an element of the structure.
403 */
404 void touch(const Const_iterator& it);
405
406 /**
407 * Given a key into the structure, makes the corresponding element "newest" by moving it from wherever it
408 * is to be first in the iteration order; or does nothing if no such key. `find(key)` equivalent is performed
409 * first. Return value indicates whether it was found.
410 *
411 * @param key
412 * Key whose equal to find.
413 * @return `true` if the key was found (even if it was already "newest"); `false` if not found.
414 */
415 bool touch(const Key& key);
416
417 /**
418 * Erases the element pointed to by the given valid iterator. Behavior undefined if it is not valid. `it` becomes
419 * invalid.
420 *
421 * @param it
422 * Iterator of element to erase.
423 * @return Iterator one position past (i.e., "older") than `it`, before `*it` was removed.
424 */
426
427 /**
428 * Erases all elements in the range [`it_newest`, `it_past_oldest`). Behavior undefined if a given iterator is
429 * invalid, or if the range is invalid. Corner case: an empty range is allowed; then this no-ops. Unless no-op,
430 * `it_newest` becomes invalid.
431 *
432 * @param it_newest
433 * Iterator of first ("newest") element to erase.
434 * @param it_past_oldest
435 * Iterator of one past last ("oldest") element to erase.
436 * @return `it_past_oldest` copy.
437 */
438 Iterator erase(const Const_iterator& it_newest, const Const_iterator& it_past_oldest);
439
440 /**
441 * Erases the element with the given key, if it exists. `find(key)` equivalent is performed
442 * first. Return value indicates whether it existed.
443 *
444 * @param key
445 * Key such that its equal's (if found) element will be erased.
446 * @return Number of elements erased (0 or 1).
447 */
448 size_type erase(const Key& key);
449
450 /// Makes it so that `size() == 0`.
451 void clear();
452
453 /**
454 * Synonym of newest().
455 * @return See newest().
456 */
458
459 /**
460 * Returns first, a/k/a "newest," element's iterator; or past_oldest() if empty().
461 * @return Ditto.
462 */
464
465 /**
466 * Synonym of past_oldest(). Exists as standard container method.
467 * @return See past_oldest().
468 */
470
471 /**
472 * Returns special iterator indicating the position just past the iteration order; if not empty() this is
473 * one past last, a/k/a "oldest," element in the iteration order.
474 *
475 * @return Ditto.
476 */
478
479 /**
480 * Synonym of const_newest(). Exists as standard container method (hence the odd formatting).
481 * @return See const_newest().
482 */
484
485 /**
486 * Synonym of cbegin(). Exists to satisfy the C++11 rangy stuff (which makes `for(:)` -- and other magic -- work).
487 * @return See cbegin().
488 */
490
491 /**
492 * Same as newest() but operating on immutable `*this`.
493 * @return Ditto.
494 */
496
497 /**
498 * Synonym of const_past_oldest(). Exists as standard container method (hence the odd formatting).
499 * @return See const_past_oldest().
500 */
502
503 /**
504 * Synonym of cend(). Exists to satisfy the C++11 rangy stuff (which makes `for(:)` -- and other magic -- work).
505 * @return See cend().
506 */
508
509 /**
510 * Same as past_oldest() but operating on immutable `*this`.
511 * @return Ditto.
512 */
514
515 /**
516 * Synonym of oldest().
517 * @return See oldest().
518 */
520
521 /**
522 * Returns first, a/k/a "oldest," element's reverse iterator.
523 * @return Ditto.
524 */
526
527 /**
528 * Synonym of past_newest(). Exists as standard container method.
529 * @return See past_newest().
530 */
532
533 /**
534 * Returns one past last, a/k/a "newest," element's reverse iterator.
535 * @return Ditto.
536 */
538
539 /**
540 * Synonym of const_oldest().
541 * @return See const_oldest().
542 */
544
545 /**
546 * Returns first, a/k/a "oldest," element's reverse iterator (to immutable element).
547 * @return Ditto.
548 */
550
551 /**
552 * Synonym of const_past_newest(). Exists as standard container method.
553 * @return See const_past_newest().
554 */
556
557 /**
558 * Returns special reverse iterator indicating the position just past the reverse-iteration order; if not empty()
559 * this is one past last, a/k/a "newest," element in the reverse-iteration order.
560 * @return Ditto.
561 */
563
564 /**
565 * Returns true if and only if container is empty. Same performance as of `unordered_set<>`.
566 * @return Ditto.
567 */
568 bool empty() const;
569
570 /**
571 * Returns number of elements stored. Same performance as of `unordered_set<>.`
572 * @return Ditto.
573 */
575
576 /**
577 * Returns max number of elements that can be stored. Same performance as of `unordered_set<>` + `list<>`.
578 * @return Ditto.
579 */
581
582private:
583
584 // Methods.
585
586 /**
587 * Helper that modifies #m_value_list and #m_value_iter_set so that `key_and_mapped`'s copy is inserted into
588 * the structure. Pre-condition is that `key_and_mapped.first` is not in the structure (else behavior undefined).
589 *
590 * @param key_and_mapped
591 * Same as in insert().
592 * @return Same as in `insert().first`.
593 */
594 Iterator insert_impl(const Value& key_and_mapped);
595
596 /**
597 * Similar to insert_impl(), except `key_and_mapped` components are `move()`d into `*this` instead of being copied.
598 *
599 * @param key_and_mapped
600 * Same as in insert().
601 * @return Same as in `insert().first`.
602 */
604
605 // Data.
606
607 /**
608 * The actual values -- which, as in `unordered_map<K, M>`, are instances of #Value = `pair<const Key, Mapped>` --
609 * are stored in here, in the order in which user would iterate over them. If `Value v` is in this list, then no
610 * `Value v1 == v` can be elsewhere in the list. The order is semantically defined to be from "newest" to "oldest."
611 * Therefore, any newly inserted value goes at the *start* of the list. Similarly, any "touched" value is moved to
612 * the *start* of the list (see touch()).
613 *
614 * This ordering is what a normal `unordered_map<K, M>` would not supply (it's in the name!) but that we advertise.
615 *
616 * ### Design ###
617 * This is very much the central structure in a `*this`; its iterator type *is* our exposed #Iterator.
618 * Straight-up, #m_value_list supplies every single required operation (or at least ones on top of which any
619 * required ops could be implemented). There is exactly one exception to this: `find(const Key&)`. It too
620 * could be implemented with #m_value_list alone, but a linear search (linear-time worst- and average-case)
621 * would be necessary (unacceptable). Because of that we have #m_value_iter_set. See that guy's doc header.
622 *
623 * ### Performance ###
624 * Moving a value from anywhere to either end of the list is a constant-time operation
625 * (assuming the source location's iterator is known). Hence touch() is constant-time. Moreover, touch()
626 * does *not* involve a copy of a #Value (it only involves assigning, internally, a few linked list pointers).
627 * Also note that insertion is similarly constant-time. Finally, erasure is also constant-time. These are the
628 * basic operations needed.
629 */
631
632 /**
633 * Data structure that allows the amortized-constant-time (as in `unordered_set`) implementation of
634 * `this->find(key)`, where `key` is `const Key&`. Namely, then, given a #Key, it gets us an #Iterator
635 * into #m_value_list -- the central data store -- or a null iterator if not-found.
636 *
637 * ### Design ###
638 * (There is quick intro in Impl section of the class doc header.) Ignoring various technicalities and C++isms,
639 * ultimately it stores `Iterator`s while supporting the **find** operation that
640 *
641 * - takes a `const Key& key`; and
642 * - yields the #Iterator `it` stored therein (if any) such that
643 * - it->second *equals by value* (via #Hash and #Pred) the `key`.
644 *
645 * This find-op must #Hash the `key`; and then perform a (series of) #Pred comparisons between
646 * `key` and the `Key`s stored at `Iterator`s within that hash-bucket.
647 *
648 * *How* this is accomplished is encapsulated inside #Linked_hash_key_set, Linked_hash_key, Linked_hash_key_hash,
649 * and Linked_hash_key_pred helper (internally-used only) types. This is abstracted away; the bottom line is
650 * `m_value_iter_set.find(key)->iter()` yields the proper #Iterator (assuming `.find()` didn't yield `.end()`).
651 *
652 * Similarly `m_value_iter_set.insert(iter)` -- where `iter` is an #Iterator into #m_value_list -- just works.
653 *
654 * ### Performance ###
655 * Anything they'll need to do to this set (namely `.find()` and `.insert()`) carries the same performance cost as
656 * if they used a straight `unordered_map<>`, so by definition it is acceptable.
657 */
659}; // class Linked_hash_map
660
661// Free functions: in *_fwd.hpp.
662
663// Template implementations.
664
665template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
667 const Hash& hasher_obj,
668 const Pred& pred) :
669 Linked_hash_map({}, n_buckets, hasher_obj, pred)
670{
671 // That's all.
672}
673
674template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
676 size_type n_buckets,
677 const Hash& hasher_obj,
678 const Pred& pred) :
679 // Their initializer_list is meant for a dictionary, but it is perfect for our list of pairs!
680 m_value_list(values),
681 /* @todo Using detail:: like this is technically uncool, but so far all alternatives look worse.
682 * We blame the somewhat annoying ctor API for unordered_*. */
683 m_value_iter_set((n_buckets == size_type(-1))
684 ? boost::unordered::detail::default_bucket_count
685 : n_buckets,
686 hasher_obj, pred)
687{
688 // Now link each key in the quick-lookup table to its stored location in the ordering.
689 const auto value_list_end_it = m_value_list.end();
690 for (auto value_list_it = m_value_list.begin(); value_list_it != value_list_end_it; ++value_list_it)
691 {
692 // Note that value_list_it contains both the iterator (lookup result) and the lookup key (iterator pointee).
693 m_value_iter_set.insert(value_list_it);
694 }
695}
696
697template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
699 // An empty m_value_iter_set is constructed here but immediately replaced within the {body}.
700{
701 operator=(src);
702}
703
704template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
706 // An empty m_value_iter_set is constructed here but immediately replaced within the {body}.
707{
708 operator=(std::move(src));
709}
710
711template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
714{
715 using Value_iter_set = decltype(m_value_iter_set);
716
717 if (&src == this)
718 {
719 return *this;
720 }
721 // else
722
723 /* Values are values -- copy them over. Recall these are (const Key, Mapped) pairs.
724 *
725 * Why not just: `m_value_list = src.m_value_list;`? Answer: It fails to build, at least with a
726 * clang++, due to an interesting subtlety. Recall that the list stores (const Key, Mapped) pairs: note
727 * the `const`. Say you have iterator `it` into m_value_list; then `*it = <...>;` will not compile, as
728 * *it is partially const and cannot be assigned to (it is not "Assignable," in STL-speak). Yet the STL
729 * implementation in question will indeed perform that operation. But why? To make the new m_value_list,
730 * just create node after node, each one storing a copy of the source node's corresponding (const Key, Mapped)
731 * (a/k/a Value!) pair, right? That would only require that Value (and therefore `const Key`, which is
732 * half of it) be CopyConstructible (again, in STL-speak, which basically matches English here). Yes, but this
733 * implementation, for performance, instead keeps m_value_list's existing node structure intact, only *assigning*
734 * over (via operator=(), not via copy constructor!) the stored value inside each node; only "resorting" to
735 * making new nodes and copy-constructing things once it has run out of m_value_list's own nodes. Clever but
736 * messes us over.
737 *
738 * Note that this can't be worked around by merely performing an m_value_list.clear() before the list assignment;
739 * the code is still the code (even if it would not run in actuality due to the pre-clearing) and wouldn't
740 * compile. However, by writing it as an insert(), it will absolutely force it to make a bunch of copies of the
741 * nodes, ensuring it compiles and works fine:
742 * m_value_list.clear();
743 * m_value_list.insert(m_value_list.end(), src.m_value_list.begin(), src.m_value_list.end());
744 *
745 * That's fine. However, it does actually bypass a nice performance trick! We would rather not make that
746 * concession. Therefore, let's temporarily pretend m_value_list and src.m_value_list store non-const Keys.
747 * Then we can assign. Note that, even though it's N lines of code, reinterpret_cast<> generates no machine
748 * code: it just makes the code-that-would-have-been-generated-anyway look at the memory in a different way
749 * (in this case, as storing Keys that can be overwritten instead of read-only ones). Finally, note that
750 * we specifically require that the template parameter `typename Key` be Assignable; that is the piece of the
751 * puzzle that GUARANTEES this reinterpret_cast<> (in general not a safe operation) is indeed safe/correct. */
752 {
753 using Mutable_key_value_list = std::list<Value_movable>;
754
755 *(reinterpret_cast<Mutable_key_value_list*>(&m_value_list))
756 = *(reinterpret_cast<const Mutable_key_value_list*>(&src.m_value_list));
757 }
758
759 /* However, the iterators in any hash set would point into the wrong list! Build up that set from scratch.
760 * Do try to keep the same structure, as we advertise. */
761 const auto& src_value_iter_set = src.m_value_iter_set;
762 m_value_iter_set = Value_iter_set{src_value_iter_set.bucket_count(),
763 src_value_iter_set.hash_function(),
764 src_value_iter_set.key_eq()};
765
766 const auto value_list_end_it = m_value_list.end();
767 for (auto value_list_it = m_value_list.begin(); value_list_it != value_list_end_it; ++value_list_it)
768 {
769 // Note that value_list_it contains both the iterator (lookup result) and the lookup key (iterator pointee).
770 m_value_iter_set.insert(value_list_it);
771 }
772
773 return *this;
774} // Linked_hash_map::operator=()
775
776template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
779{
780 if (&src != this)
781 {
782 clear();
783 swap(src);
784 }
785 return *this;
786}
787
788template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
790{
791 using std::swap;
792
793 swap(m_value_iter_set, other.m_value_iter_set); // unordered_set<> exchange; constant-time for sure at least.
794 swap(m_value_list, other.m_value_list); // list<> exchange (probably ~= head+tail pointer pairs exchanged).
795 // Per cppreference.com `list<>::iterator`s (inside the `_maps`s) remain valid after list<>s swapped.
796}
797
798template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
799std::pair<typename Linked_hash_map<Key_t, Mapped_t, Hash_t, Pred_t>::Iterator, bool>
801{
802 using std::pair;
803
804 const auto set_it = m_value_iter_set.find(key_and_mapped.first);
805 return (set_it == m_value_iter_set.end())
806 ? pair<Iterator, bool>{insert_impl(key_and_mapped), // Key and Mapped copy occurs here.
807 true}
808 : pair<Iterator, bool>{set_it->iter(), false}; // *set_it is Linked_hash_key.
809}
810
811template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
812std::pair<typename Linked_hash_map<Key_t, Mapped_t, Hash_t, Pred_t>::Iterator, bool>
814{
815 using std::pair;
816
817 const auto set_it = m_value_iter_set.find(key_and_mapped.first);
818 return (set_it == m_value_iter_set.end())
819 ? pair<Iterator, bool>{insert_impl_mv(std::move(key_and_mapped)), // <-- The difference from other overload.
820 true}
821 : pair<Iterator, bool>{set_it->iter(), false}; // *set_it is Linked_hash_key.
822}
823
824template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
826{
827 const auto set_it = m_value_iter_set.find(key);
828 return ((set_it == m_value_iter_set.end())
829 ? insert_impl_mv // Returns Iterator. *(Iterator) is pair<const Key, Mapped>.
830 (Value_movable{Key{key}, Mapped{}}) // Have to copy `key`, but empty temporary Mapped is moved.
831 : set_it->iter()) // *set_it is Linked_hash_key. *(that.iter()) is pair<const Key, Mapped>.
832 ->second;
833}
834
835template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
837{
838 const auto set_it = m_value_iter_set.find(key);
839 return ((set_it == m_value_iter_set.end())
840 // v-- The difference from other overload.
841 ? insert_impl_mv // Returns Iterator. *(Iterator) is pair<const Key, Mapped>.
842 (Value_movable{std::move(key), Mapped{}})
843 : set_it->iter()) // *set_it is Linked_hash_key. *(that.iter()) is pair<const Key, Mapped>.
844 ->second;
845}
846
847template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
848typename Linked_hash_map<Key_t, Mapped_t, Hash_t, Pred_t>::Iterator
850{
851 /* Insert it at the front: as advertised, new element is "touched," meaning it is made "newest," so goes at start.
852 * Note that "it" = a copy of key_and_mapped; this invokes pair<> copy ctor, as emplace_front() forwards to it. */
853 m_value_list.emplace_front(key_and_mapped);
854
855 /* Iterator to the new element is therefore iterator to start of list of <Key, Mapped> pairs.
856 * And make sure we can look it up in the future quickly (such as what is done first in insert()).
857 * Linked_hash_key_set m_value_iter_set achieves these aims black-boxily. */
858 const auto list_it = m_value_list.begin();
859 m_value_iter_set.insert(list_it);
860 return list_it;
861}
862
863template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
866{
867 /* Same as insert_impl() but construct value in-place inside the list<> as-if:
868 * pair<const Key, Mapped> p{move(k_a_m.first), move(k_a_m.second)}. */
869 m_value_list.emplace_front(std::move(key_and_mapped));
870
871 const auto list_it = m_value_list.begin();
872 m_value_iter_set.insert(list_it);
873 return list_it;
874}
875
876template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
879{
880 const auto set_it = m_value_iter_set.find(key);
881 return (set_it == m_value_iter_set.end()) ? m_value_list.end() : set_it->iter();
882}
883
884template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
887{
888 const auto set_it = m_value_iter_set.find(key);
889 return (set_it == m_value_iter_set.cend()) ? m_value_list.cend() : set_it->iter();
890}
891
892template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
895{
896 return m_value_iter_set.count(key);
897}
898
899template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
901{
902 m_value_list.splice(m_value_list.begin(), m_value_list, it);
903}
904
905template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
907{
908 const auto it = find(key);
909 if (it == end())
910 {
911 return false;
912 }
913 // else
914
915 touch(it);
916 return true;
917}
918
919template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
922{
923 m_value_iter_set.erase(it->first);
924 // (^-- Subtlety: .erase(it) won't build due to Const_ness of `it`.)
925
926 return m_value_list.erase(it);
927}
928
929template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
932 const Const_iterator& it_past_oldest)
933{
934 for (auto it = it_newest; it != it_past_oldest; ++it)
935 {
936 m_value_iter_set.erase(it->first);
937 }
938
939 return m_value_list.erase(it_newest, it_past_oldest);
940}
941
942template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
945{
946 const auto set_it = m_value_iter_set.find(key);
947 if (set_it == m_value_iter_set.end())
948 {
949 return 0;
950 }
951 // else
952
953 const auto list_it = set_it->iter();
954 m_value_iter_set.erase(set_it);
955 m_value_list.erase(list_it);
956
957 return 1;
958}
959
960template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
962{
963 m_value_iter_set.clear();
964 m_value_list.clear();
965}
966
967template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
970{
971 return m_value_list.begin();
972}
973
974template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
977{
978 return newest();
979}
980
981template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
984{
985 return m_value_list.end();
986}
987
988template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
991{
992 return past_oldest();
993}
994
995template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
998{
999 return m_value_list.cbegin();
1000}
1001
1002template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1005{
1006 return const_newest();
1007}
1008
1009template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1012{
1013 return const_newest();
1014}
1015
1016template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1019{
1020 return m_value_list.cend();
1021}
1022
1023template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1026{
1027 return const_past_oldest();
1028}
1029
1030template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1033{
1034 return const_past_oldest();
1035}
1036
1037template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1040{
1041 return m_value_list.rbegin();
1042}
1043
1044template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1047{
1048 return oldest();
1049}
1050
1051template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1054{
1055 return m_value_list.rend();
1056}
1057
1058template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1061{
1062 return past_newest();
1063}
1064
1065template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1068{
1069 return m_value_list.crbegin();
1070}
1071
1072template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1075{
1076 return const_oldest();
1077}
1078
1079template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1082{
1083 return m_value_list.crend();
1084}
1085
1086template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1089{
1090 return const_past_newest();
1091}
1092
1093template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1096{
1097 return m_value_iter_set.size(); // I'm skeptical/terrified of list::size()'s time complexity.
1098}
1099
1100template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1102{
1103 return m_value_list.empty();
1104}
1105
1106template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1109{
1110 return std::min(m_value_iter_set.max_size(), m_value_list.max_size());
1111}
1112
1113template<typename Key_t, typename Mapped_t, typename Hash_t, typename Pred_t>
1116{
1117 val1.swap(val2);
1118}
1119
1120} // namespace flow::util
An object of this class is a map that combines the lookup speed of an unordered_map<> and ordering an...
size_type max_size() const
Returns max number of elements that can be stored.
Reverse_iterator past_newest()
Returns one past last, a/k/a "newest," element's reverse iterator.
Const_iterator cbegin() const
Synonym of const_newest().
typename Value_list::iterator Iterator
Type for iterator pointing into a mutable structure of this type.
std::size_t size_type
Expresses sizes/lengths of relevant things.
Const_reverse_iterator crend() const
Synonym of const_past_newest().
Value * pointer
For container compliance (hence the irregular capitalization): pointer to Key/Mapped pair type.
Iterator iterator
For container compliance (hence the irregular capitalization): Iterator type.
Mapped & operator[](const Key &key)
Either finds the Mapped value at the given key, or if not found inserts one with a default-constructe...
std::pair< Key, Mapped > Value_movable
Short-hand for key/mapped-value pair best-suited (perf-wise) as arg type for the moving insert() over...
std::pair< Key const, Mapped > Value
Short-hand for key/mapped-value pairs stored in the structure.
Key_t Key
Convenience alias for template arg.
Mapped mapped_type
For container compliance (hence the irregular capitalization): Mapped type.
Const_iterator end() const
Synonym of cend().
Hash_t Hash
Convenience alias for template arg.
Const_iterator begin() const
Synonym of cbegin().
void touch(const Const_iterator &it)
Given a valid iterator into the structure, makes the pointed-to element "newest" by moving it from wh...
std::list< Value > Value_list
Short-hand for doubly linked list of (Key, Mapped) pairs.
Reverse_iterator rbegin()
Synonym of oldest().
Iterator insert_impl_mv(Value_movable &&key_and_mapped)
Similar to insert_impl(), except key_and_mapped components are move()d into *this instead of being co...
Reverse_iterator oldest()
Returns first, a/k/a "oldest," element's reverse iterator.
Pred key_equal
For container compliance (hence the irregular capitalization): Pred type.
Value value_type
For container compliance (hence the irregular capitalization): Key/Mapped pair type.
Iterator newest()
Returns first, a/k/a "newest," element's iterator; or past_oldest() if empty().
Linked_hash_key_set< Key, Iterator, Hash, Pred, true > m_value_iter_set
Data structure that allows the amortized-constant-time (as in unordered_set) implementation of this->...
typename Value_list::const_reverse_iterator Const_reverse_iterator
Type for reverse iterator pointing into an immutable structure of this type.
Const_iterator cend() const
Synonym of const_past_oldest().
Const_iterator find(const Key &key) const
Identical to the other overload but in a const context: the returned iterator is to immutable memory.
Iterator erase(const Const_iterator &it_newest, const Const_iterator &it_past_oldest)
Erases all elements in the range [it_newest, it_past_oldest).
Iterator begin()
Synonym of newest().
Iterator erase(const Const_iterator &it)
Erases the element pointed to by the given valid iterator.
Const_iterator const_iterator
For container compliance (hence the irregular capitalization): Const_iterator type.
std::pair< Iterator, bool > insert(Value_movable &&key_and_mapped)
Identical to the other overload, except that (if key not already present in *this) the key and mapped...
const Value & const_reference
For container compliance (hence the irregular capitalization): reference to const Key/Mapped pair typ...
Value & reference
For container compliance (hence the irregular capitalization): reference to Key/Mapped pair type.
Mapped_t Mapped
Convenience alias for template arg.
Hash hasher
For container compliance (hence the irregular capitalization): Hash type.
Reverse_iterator rend()
Synonym of past_newest().
Const_reverse_iterator crbegin() const
Synonym of const_oldest().
size_type count(const Key &key) const
Returns the number of times a key equal to the given one is present (as-if via find()) in the map: ei...
std::ptrdiff_t difference_type
Type for difference of size_types.
Iterator past_oldest()
Returns special iterator indicating the position just past the iteration order; if not empty() this i...
Const_reverse_iterator const_past_newest() const
Returns special reverse iterator indicating the position just past the reverse-iteration order; if no...
size_type size() const
Returns number of elements stored.
Iterator find(const Key &key)
Attempts to find value at the given key in the map.
Linked_hash_map(std::initializer_list< Value > values, size_type n_buckets=size_type(-1), const Hash &hasher_obj=Hash{}, const Pred &pred=Pred{})
Constructs structure with some basic parameters, and values initialized from initializer list.
typename Value_list::const_iterator Const_iterator
Type for iterator pointing into an immutable structure of this type.
Iterator end()
Synonym of past_oldest().
Key key_type
For container compliance (hence the irregular capitalization): Key type.
const Value * const_pointer
For container compliance (hence the irregular capitalization): pointer to const Key/Mapped pair type.
Value_list m_value_list
The actual values – which, as in unordered_map<K, M>, are instances of Value = pair<const Key,...
void swap(Linked_hash_map &other)
Swaps the contents of this structure and other.
std::pair< Iterator, bool > insert(const Value &key_and_mapped)
Attempts to insert (copying both key and mapped-value) the given key/mapped-value pair into the map; ...
void clear()
Makes it so that size() == 0.
typename Value_list::reverse_iterator Reverse_iterator
Type for reverse iterator pointing into a mutable structure of this type.
Linked_hash_map & operator=(Linked_hash_map &&src)
Overwrites this object making it identical to the given source, while the given source becomes as-if ...
Const_iterator const_newest() const
Same as newest() but operating on immutable *this.
Const_iterator const_past_oldest() const
Same as past_oldest() but operating on immutable *this.
size_type erase(const Key &key)
Erases the element with the given key, if it exists.
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...
bool touch(const Key &key)
Given a key into the structure, makes the corresponding element "newest" by moving it from wherever i...
Linked_hash_map & operator=(const Linked_hash_map &src)
Overwrites this object with a copy of the given source.
Linked_hash_map(const Linked_hash_map &src)
Constructs object that is a copy of the given source.
Mapped & operator[](Key &&key)
Identical to the other overload, except that (if key not already present in *this) the key is moved,...
Pred_t Pred
Convenience alias for template arg.
Linked_hash_map(size_type n_buckets=size_type(-1), const Hash &hasher_obj=Hash{}, const Pred &pred=Pred{})
Constructs empty structure with some basic parameters.
Const_reverse_iterator const_oldest() const
Returns first, a/k/a "oldest," element's reverse iterator (to immutable element).
Iterator insert_impl(const Value &key_and_mapped)
Helper that modifies m_value_list and m_value_iter_set so that key_and_mapped's copy is inserted into...
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:31
void swap(Basic_blob< Allocator, SHARING > &blob1, Basic_blob< Allocator, SHARING > &blob2, log::Logger *logger_ptr) noexcept
Equivalent to blob1.swap(blob2).
void swap(Linked_hash_map< Key_t, Mapped_t, Hash_t, Pred_t > &val1, Linked_hash_map< Key_t, Mapped_t, Hash_t, Pred_t > &val2)
Equivalent to val1.swap(val2).