Flow 2.0.0
Flow project: Full implementation reference.
linked_hash.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
22#include <variant>
23
24namespace flow::util
25{
26
27// Types.
28
29/**
30 * The internal-use key/iterator-wrapper, used as the key type in internal-use set-type #Linked_hash_key_set.
31 *
32 * For background please see Linked_hash_map doc header Impl section, and where that points. Note that
33 * the same information applies to Linked_hash_set as well. (`IS_ITER_TO_PAIR` indicates which guy this is helping
34 * implement.)
35 *
36 * That said, we very specifically just need to:
37 *
38 * - store either a #Key pointer (when a `*this` is used as the arg to `Linked_hash_key_set::find()` or an
39 * #Iterator copy (ditto but to `Linked_hash_key_set::insert()`);
40 * - be implicit-constructible from either (so the `Key` or `Iterator` can be passed seamlessly into
41 * `.find()` and `.insert()` respectively);
42 * - in the iterator-storing case provide access to that iterator via iter() accessor.
43 *
44 * @tparam Key_t
45 * See Linked_hash_map, Linked_hash_set.
46 * @tparam Iterator_t
47 * Linked_hash_map::Iterator or Linked_hash_set::Iterator.
48 * @tparam IS_ITER_TO_PAIR
49 * `true` for Linked_hash_map, `false` for Linked_hash_set.
50 */
51template<typename Key_t, typename Iterator_t, bool IS_ITER_TO_PAIR>
53{
54public:
55 // Types.
56
57 /// Convenience alias for template arg.
58 using Key = Key_t;
59
60 /// Convenience alias for template arg.
61 using Iterator = Iterator_t;
62
63 // Constants.
64
65 /// Convenience alias for template arg.
66 static constexpr bool S_IS_ITER_TO_PAIR = IS_ITER_TO_PAIR;
67
68 // Constructors/destructor.
69
70 /**
71 * Constructs `*this` to contain a pointer to a #Key living outside the `*this`-using data structure
72 * #Linked_hash_key_set (presumably as arg to its `.find()`).
73 *
74 * @param key
75 * The key to which to point. The referred object must continue to be valid, until either
76 * `*this` is destroyed, or via assignment `*this` changes value.
77 */
78 Linked_hash_key(const Key& key);
79
80 /**
81 * Constructs `*this` to contain an #Iterator into the `*this`-using data structure
82 * #Linked_hash_key_set (presumably as arg to its `.insert()`).
83 *
84 * @param it
85 * The iterator a copy of which to store. The pointee of the iterator must not be erased or moved in
86 * an iterator-invalidating fashion, until either
87 * `*this` is destroyed, or via assignment `*this` changes value.
88 */
89 Linked_hash_key(const Iterator& it);
90
91 // Methods.
92
93 /**
94 * Returns reference to immutable key to which we saved a pointer via one of our constructor forms.
95 * Specifically, then, that's either simply the ctor-saved pointer (in reference form); or the address of the key
96 * stored as stored within the `*this`-using data structure #Linked_hash_key_set.
97 *
98 * @return See above.
99 */
100 const Key& key() const;
101
102 /**
103 * Assuming we were as-if constructed via the ctor that takes an #Iterator (as opposed to a #Key reference),
104 * returns copy of the stored iterator. Behavior undefined if the assumption does not hold (as of this writing
105 * exception is thrown).
106 *
107 * Informally:
108 * if `this->key()` *was* found in the #Linked_hash_key_set, then this accessor may be used; else it may not.
109 * That `*this`-using data structure -- a hash-set -- shall use Linked_hash_key_hash and Linked_hash_key_pred for its
110 * hasher and equality functors respectively.
111 *
112 * @return See above.
113 */
114 Iterator iter() const;
115
116private:
117 // Types.
118
119 /// Short-hand for raw pointer to an immutable #Key living outside the `*this`-using data structure.
120 using Key_ptr = Key const *;
121
122 // Data.
123
124 /**
125 * Stores the key, without copying its actual value, as either a pointer to an immutable key, or as an iterator
126 * into a structure such that it contains the key. In the latter case:
127 *
128 * - #S_IS_ITER_TO_PAIR determines how to obtain the key from the iterator pointee;
129 * - the iterator is itself of value to the `Linked_hash_key_*` user (Linked_hash_set, Linked_hash_map).
130 */
131 std::variant<Key_ptr, Iterator> m_key_hndl;
132}; // class Linked_hash_key
133
134/**
135 * The internal-use `Hash` functor wrapper, used as the hasher type in internal-use set-type #Linked_hash_key_set.
136 *
137 * Impl note: We store the `Hash` via private inheritance, making use of Empty Base-class Optimization (EBO) if
138 * possible; namely when `Hash` is an empty type (which is typical); in which case this shall waste no space.
139 *
140 * @tparam Hash
141 * See Linked_hash_map, Linked_hash_set.
142 */
143template<typename Hash>
145 private Hash
146{
147public:
148 // Constructors/destructor.
149
150 /**
151 * Saves a (typically data-free) copy of a given Linked_hash_map or Linked_hash_set hasher object,
152 * as passed to that type's ctor.
153 *
154 * @param hasher
155 * See above.
156 */
157 Linked_hash_key_hash(const Hash& hasher = Hash{});
158
159 // Methods.
160
161 /**
162 * Returns hash of `val.key()`, where `val` is a Linked_hash_key instance, using the saved hasher object.
163 *
164 * @tparam Linked_hash_key_t
165 * A concrete Linked_hash_key type: the key-type of #Linked_hash_key_set being used.
166 * @param val
167 * Value to hash.
168 * @return See above.
169 */
170 template<typename Linked_hash_key_t>
171 size_t operator()(const Linked_hash_key_t& val) const;
172}; // class Linked_hash_key_hash
173
174/**
175 * The internal-use `Pred` functor wrapper, used as the key-equality-determiner type in internal-use set-type
176 * #Linked_hash_key_set.
177 *
178 * Impl note: We store the `Pred` via private inheritance, making use of Empty Base-class Optimization (EBO) if
179 * possible; namely when `Pred` is an empty type (which is typical); in which case this shall waste no space.
180 *
181 * @tparam Pred
182 * See Linked_hash_map, Linked_hash_set.
183 */
184template<typename Pred>
186 private Pred
187{
188public:
189 // Constructors/destructor.
190
191 /**
192 * Saves a (typically data-free) copy of a given Linked_hash_map or Linked_hash_set equality-determiner object,
193 * as passed to that type's ctor.
194 *
195 * @param pred
196 * See above.
197 */
198 Linked_hash_key_pred(const Pred& pred = Pred{});
199
200 // Methods
201
202 /**
203 * Returns `true` if and only if `lhs.key()` and `rhs.key()` (where `lhs` and `rhs` are Linked_hash_key instances)
204 * are equal, using the saved equality-determiner object.
205 *
206 * @tparam Linked_hash_key_t
207 * A concrete Linked_hash_key type.
208 * @param lhs
209 * Value to compare.
210 * @param rhs
211 * Value to compare.
212 * @return See above.
213 */
214 template<typename Linked_hash_key_t>
215 bool operator()(const Linked_hash_key_t& lhs, const Linked_hash_key_t& rhs) const;
216}; // class Linked_hash_key_pred
217
218// Template implementations.
219
220// Linked_hash_key implementations.
221
222template<typename Key_t, typename Iterator_t, bool IS_ITER_TO_PAIR>
224 m_key_hndl(&key) // Store a Key* in the union.
225{
226 // Yep.
227}
228
229template<typename Key_t, typename Iterator_t, bool IS_ITER_TO_PAIR>
231 m_key_hndl(it) // Store an Iterator in the union.
232{
233 // Yep.
234}
235
236template<typename Key_t, typename Iterator_t, bool IS_ITER_TO_PAIR>
237const Key_t&
239{
240 using std::holds_alternative;
241 using std::get;
242
243 if (holds_alternative<Key_ptr>(m_key_hndl))
244 {
245 return *(get<Key_ptr>(m_key_hndl));
246 }
247 // else
248
249 if constexpr(S_IS_ITER_TO_PAIR)
250 {
251 return get<Iterator>(m_key_hndl)->first; // Iterator into list of `const Key`s.
252 }
253 else
254 {
255 return *(get<Iterator>(m_key_hndl)); // Iterator into list of pair<const Key, Mapped>s.
256 }
257}
258
259template<typename Key_t, typename Iterator_t, bool IS_ITER_TO_PAIR>
260Iterator_t
262{
263 return std::get<Iterator>(m_key_hndl);
264 // ^-- Throws if !holds_alternative<Iterator>(m_key_hndl); we advertised undefined behavior; so that's fine.
265}
266
267// Linked_hash_key_hash implementations.
268
269template<typename Hash>
271 Hash(hasher) // Store `hasher` copy in our super-class, making use of Empty Base-class Optimization (EBO) if possible.
272{
273 /* For context: A regular unordered_set<Key, Hash, ...> would store the `Hash hasher` copy inside itself.
274 * In our case it is unordered_set<Linked_hash_key, Linked_hash_key_hash<Hash>, ...> instead, so a *this is
275 * instead stored; and we store the original `Hash hasher` inside us (and nothing else). So it's the exact same
276 * thing in terms of what actually ends up in memory and likely in terms of processor cycles spent.
277 *
278 * Why do we even exist? Answer: Just for the extra little code in operator()(). */
279}
280
281template<typename Hash>
282template<typename Linked_hash_key_t>
283size_t Linked_hash_key_hash<Hash>::operator()(const Linked_hash_key_t& val) const
284{
285 return this->Hash::operator()(val.key()); // The piddly .key() call is the reason this class exists.
286}
287
288// Linked_hash_key_pred implementations.
289
290template<typename Pred>
292 Pred(pred) // Store `pred` copy in our super-class, making use of Empty Base-class Optimization (EBO) if possible.
293{
294 /* For context: A regular unordered_set<Key, ..., Pred> would store the `Pred pred` copy inside itself.
295 * In our case it is unordered_set<Linked_hash_key, ..., Linked_hash_key_pred<PRed>> instead, so a *this is
296 * instead stored; and we store the original `Pred pred` inside us (and nothing else). So it's the exact same
297 * thing in terms of what actually ends up in memory and likely in terms of processor cycles spent.
298 *
299 * Why do we even exist? Answer: Just for the extra little code in operator()(). */
300}
301
302template<typename Pred>
303template<typename Linked_hash_key_t>
304bool Linked_hash_key_pred<Pred>::operator()(const Linked_hash_key_t& lhs, const Linked_hash_key_t& rhs) const
305{
306 return this->Pred::operator()(lhs.key(), rhs.key()); // The piddly .key() calls are the reason this class exists.
307
308 /* @todo Arguably this could be sped-up in the case where lhs and rhs both hold `Iterator`s
309 * (holds_alternative<Iterator>([lr]hs.m_key_hndl)); in which case one would return `lhs.iter() == rhs.iter()`.
310 * This would require formally that lhs and rhs are guaranteed to point into the same Linked_hash_{set|map}, and
311 * the formal doc for this operator()() would specify that it's not merely comparison of .key() values by Pred.
312 *
313 * "Arguably" above refers to how that might be too much trouble "just" to avoid a by-value key comparison in
314 * some cases (some cases as of this writing = Linked_hash_*::erase(<iterator-typed args>). Then again that does
315 * make sense, if one breaks down the actual use cases in Linked_hash_set/map:
316 * - lhs and rhs are both iterator-storing <=> those "some cases," namely where lookup is by user-provided iterator.
317 * - lhs is iterator-storing; rhs is key-ptr-storing <=> the other cases, namely where lookup is by user-provided
318 * key.
319 * - The reverse <=> Shouldn't happen; the "haystack" Linked_hash_key_set is on the left.
320 * - lhs and rhs are both key-ptr-storing <=> Shouldn't happen; lookup is only needed when there's a haystack
321 * involved.
322 *
323 * So we could write it that way -- perhaps assert(false)ing on the "shouldn't happen" cases -- and arguably it
324 * would be (1) conceptually tighter (serving the exact known purpose) and (2) probably faster (avoids key comparison
325 * at least sometimes). On the other hand internal documentation would be more complex, and the code here would
326 * be longer/more complex (probably requiring among other things either `friend`ship or additional accessor(s) on
327 * Linked_hash_key.
328 *
329 * Oh, and also, I (ygoldfel) am not 100% certain about the following, but another difficulty might arise when
330 * Iterator and Const_iterator are not the same type.... Bottom line, probably best not jump into this, unless
331 * the perf gain is really determined to be worthwhile for someone in practice. */
332}
333
334} // namespace flow::util
The internal-use Hash functor wrapper, used as the hasher type in internal-use set-type Linked_hash_k...
size_t operator()(const Linked_hash_key_t &val) const
Returns hash of val.key(), where val is a Linked_hash_key instance, using the saved hasher object.
Linked_hash_key_hash(const Hash &hasher=Hash{})
Saves a (typically data-free) copy of a given Linked_hash_map or Linked_hash_set hasher object,...
The internal-use Pred functor wrapper, used as the key-equality-determiner type in internal-use set-t...
bool operator()(const Linked_hash_key_t &lhs, const Linked_hash_key_t &rhs) const
Returns true if and only if lhs.key() and rhs.key() (where lhs and rhs are Linked_hash_key instances)...
Linked_hash_key_pred(const Pred &pred=Pred{})
Saves a (typically data-free) copy of a given Linked_hash_map or Linked_hash_set equality-determiner ...
The internal-use key/iterator-wrapper, used as the key type in internal-use set-type Linked_hash_key_...
Definition: linked_hash.hpp:53
const Key & key() const
Returns reference to immutable key to which we saved a pointer via one of our constructor forms.
std::variant< Key_ptr, Iterator > m_key_hndl
Stores the key, without copying its actual value, as either a pointer to an immutable key,...
Key_t Key
Convenience alias for template arg.
Definition: linked_hash.hpp:58
Linked_hash_key(const Key &key)
Constructs *this to contain a pointer to a Key living outside the *this-using data structure Linked_h...
static constexpr bool S_IS_ITER_TO_PAIR
Convenience alias for template arg.
Definition: linked_hash.hpp:66
Key const * Key_ptr
Short-hand for raw pointer to an immutable Key living outside the *this-using data structure.
Iterator iter() const
Assuming we were as-if constructed via the ctor that takes an Iterator (as opposed to a Key reference...
Iterator_t Iterator
Convenience alias for template arg.
Definition: linked_hash.hpp:61
Flow module containing miscellaneous general-use facilities that don't fit into any other Flow module...
Definition: basic_blob.hpp:31