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