1 module nxt.container.hybrid_hashmap;
2 
3 import core.internal.hash : hashOf;
4 import nxt.nullable_traits : isNullable;
5 import nxt.allocator_traits : isAllocator;
6 import nxt.container.common : BorrowCheckFlag;
7 import nxt.my_mallocator : Mallocator;
8 
9 enum PrimeCapacityFlag : ubyte { no, yes }
10 
11 @safe:
12 
13 /** Hash table/map with open-addressing and hybrid storage, storing `keys` of
14  * type `K` and values of type `V`. Setting `V` to `void` turns the map into a
15  * set.
16  *
17  * Keys are immutable except for when they are `class`es in which case they are
18  * head-const (through bin reinterpretation to `KeyValueType`), This can be
19  * overridden by setting `keyEqualPred` to, for instance, `a == b` for `class`
20  * keys.
21  *
22  * Uses quadratic probing (using triangular numbers) unless `usePrimeCapacity`
23  * in which case a simpler probing is used.
24  *
25  * Deletion/Removal of elements is lazy via the sentinel bitmap `_holesPtr` or
26  * through assignment of reserved value of `KeyType` when `KeyType` has
27  * hole/tombstone-sentinel-support via trait `isHoleable`. The terms "tombstone"
28  * and "sentinel" are used at, for instance,
29  * https://engineering.fb.com/2019/04/25/developer-tools/f14/ and
30  * https://www.youtube.com/watch?v=ncHmEUmJZf4&t=2837s.
31  *
32  * Element iteration via
33  * - either `byKey`, `byValue` or `byKeyValue` over `HybridHashMap` and
34  * - `byElement` over `HybridHashSet`
35  * respects taking the container argument either as an l-value or r-value using
36  * detected using `auto ref`-qualified parameter introspected using `(__traits(isRef, y))`.
37  * In the r-value case no reference counting is needed.
38  * In the l-value case setting `borrowChecked` to `true` adds run-time
39  * support for dynamic Rust-style ownership and borrowing between the range and the container.
40  *
41  * Params:
42  *     K = key type
43  *     V = value type
44  *     hasher = hash function or std.digest Hash
45  *     Allocator = memory allocator for bin array
46  *     borrowChecked = only activate when it's certain that this won't be moved via std.algorithm.mutation.move()
47  *     linearSearchMaxSize = Use linear search instead of probing when `_store.sizeof <= linearSearchMaxSize`
48  *     usePrimeCapacity = Use prime numbers as capacity of hash table enabling better performance of simpler hash-functions
49  *
50  * See_Also: https://engineering.fb.com/2019/04/25/developer-tools/f14/
51  * See_Also: https://github.com/abseil/abseil-cpp/blob/master/absl/container/flat_hash_map.h
52  * See_Also: https://www.sebastiansylvan.com/post/robin-hood-hashing-should-be-your-default-hash-table-implementation/
53  * See_Also: https://arxiv.org/abs/1605.04031
54  * See_Also: https://github.com/Tessil/robin-map
55  * See_Also: https://github.com/martinus/robin-hood-hashing
56  * See_Also: https://probablydance.com/2017/02/26/i-wrote-the-fastest-hashtable/
57  * See_Also: https://en.wikipedia.org/wiki/Lazy_deletion
58  * See_Also: https://forum.dlang.org/post/ejqhcsvdyyqtntkgzgae@forum.dlang.org
59  * See_Also: https://gankro.github.io/blah/hashbrown-insert/
60  *
61  * Test: dmd -vcolumns -preview=fieldwise -debug -g -unittest -checkaction=context -allinst -main -unittest -I../.. -i -run hybrid_hashmap.d
62  *
63  * TODO: Allocate _holesPtr array together with _store to reduce size of
64  * `HybridHashMap` to 3 words when element type doesn't support it
65  * TODO: Support non-nullable keys via extra bit-set _nullsPtr similar to _holesPtr
66  * TODO: Don’t allocate _holesPtr when we don’t need removal
67  *
68  * TODO: Add class type erasure layer to void* for the relevant members of HashMap store and factor out that store to container/store.d
69  *
70  * TODO: Make sure key classes and pointers are shifted down by alignement as
71  * is done in AlignedAddress.toHash before hashed
72  *
73  * TODO: Replace `withCapacity` with `void capacity(size_t)` like D arrays.
74  *
75  * TODO: Tests fails when `linearSearchMaxSize` is set to 0
76  *
77  * TODO: A test fails with when -preview=fieldwise is not passed.
78  *
79  * TODO: Add support for opApply by copying opApply members in
80  * ~/.dub/packages/emsi_containers-0.8.0/emsi_containers/src/containers/hashmap.d
81  *
82  * TODO: Replace remove branching on `passElementByValue` when
83  * https://github.com/dlang/dmd/pull/11000 has been merged and use `in` instead
84  *
85  * TODO: Implement `foreach (const k, const v; _)` support. See
86  * https://forum.dlang.org/post/tj700o$1412$1@digitalmars.com
87  *
88  * TODO: Disable pragma(inline, true) and rebenchmark
89  *
90  * TODO: Robin-hood case introspect key type for storage of parts of hash
91  * alongside nullValue and holeValue. Typically for address-types that doesn't
92  * need scanning. Example is `Address` for implementation of GC. This is a D
93  * showcase for code that is difficult to write in C++.
94  *
95  * TODO: group `nxt.probing` functions in `Prober` struct given as type template
96  * param to `HybridHashMap`
97  *
98  * TODO: Make load factor dependent on current capacity or length and perhaps
99  * also type and hash-function to get memory efficiency when it matters. Similar
100  * to what is recommended in https://ticki.github.io/blog/horrible/.
101  *
102  * TODO: For copyable types replace `auto ref` with logic that only passes by
103  * `ref` when it's faster to do so. See_Also:
104  * https://github.com/dlang/dmd/pull/11000. When `-preview=in` has been made
105  * the default this complexity can be removed. Or replace `ref` with `in`.
106  *
107  * TODO: Remove use of `static if (__traits(isCopyable, ...))` and `static if
108  * (__traits(isPOD, ...))` in cases where compiler can handle more moves
109  *
110  * TODO: Use mmap allocator when `_store.sizeof` is larger than at least 8 pages
111  *
112  * TODO: Use `StoreK` in store and cast between it and `KeyType`
113  *
114  * TODO: Fix bug in `growInPlaceWithCapacity` and benchmark
115  *
116  * TODO: Modify existing unittest for `struct Rel { const string name; }`
117  *
118  * TODO: Use allocator.dispose() instead of allocator.deallocate() as in
119  * https://github.com/dlang-community/containers
120  *
121  * TODO: if hash-function is cast(size_t)(classInstance) always use prime length
122  * and shift pointer before hash based on alignof (might not be needed when
123  * module prime) to maximize memory locality when adding successively allocated
124  * pointers
125  *
126  * TODO: Add extractElement that moves it out similar to
127  * http://en.cppreference.com/w/cpp/container/unordered_set/extract
128  *
129  * TODO: Add merge or union algorithm here or into container/common.d. See also:
130  * http://en.cppreference.com/w/cpp/container/unordered_set/merge. this
131  * algorithm moves elements from source if they are not already in `this`
132  *
133  * TODO: Robin-Hood-hashing
134  *
135  * TODO: Enable `borrowChecked` unconditionally in version(debug) if and when
136  * `opPostMove` is implemented, in which case opPostMove() should assert false
137  * if this is borrowed. See: https://github.com/dlang/DIPs/pull/109
138  *
139  * TODO: Save one word by making `_store.length` be inferred by
140  * `prime_modulo.primeConstants[_primeIndex]` if this is not too costly.
141  *
142  * TODO: Only add one extra element to capacity when `assumeNonFullHaystack` is
143  * `true`
144  */
145 struct HybridHashMap(K, V = void,
146 					 alias hasher = hashOf,
147 					 string keyEqualPred = defaultKeyEqualPredOf!(K),
148 					 Allocator = Mallocator,
149 					 BorrowCheckFlag borrowCheckFlag = BorrowCheckFlag.no,
150 					 uint linearSearchMaxSize = 64, // one cache-line for now
151 					 PrimeCapacityFlag primeCapacityFlag = PrimeCapacityFlag.no)
152 if (isNullable!K /*&& !hasAliasing!K */ && isAllocator!Allocator)
153 {
154     // pragma(msg, K.stringof, " => ", V.stringof);
155     import core.exception : onOutOfMemoryError;
156     import core.internal.traits : hasElaborateDestructor, Unqual;
157     import core.lifetime : move;
158     import std.traits : hasIndirections, hasFunctionAttributes;
159     import std.typecons : Nullable;
160 
161     import nxt.nullable_traits : defaultNullKeyConstantOf, isNull, nullify;
162     import nxt.container.traits : mustAddGCRange;
163     import nxt.qcmeman : gc_addRange, gc_removeRange;
164 
165 	enum usePrimeCapacity = primeCapacityFlag == PrimeCapacityFlag.yes;
166     static if (usePrimeCapacity)
167         import nxt.prime_modulo : PrimeIndex, ceilingPrime, moduloPrimeIndex;
168     else
169     {
170         static if (__VERSION__ >= 2098) import std.math.algebraic : nextPow2; else import std.math : nextPow2;
171         import nxt.probing : triangularProbeFromIndex, triangularProbeFromIndexIncludingHoles,
172 			triangularProbeCountFromIndex;
173         /// Setting this `true` doesn't give measurable speedups so set it to `false` for now.
174         enum bool assumeNonFullHaystack = false;
175     }
176 
177     static if (is(typeof(keyEqualPred) : string))
178     {
179         import std.functional : binaryFun;
180         alias keyEqualPredFn = binaryFun!keyEqualPred;
181     }
182     else
183         alias keyEqualPredFn = keyEqualPred;
184 
185 	/// Is true iff `T` is an array (slice).
186 	private enum isSlice(T) = is(T : const(E)[], E);
187 	/// Is true iff `T` is a pointer.
188 	private enum isPointer(T) = is(T == U*, U);
189 
190     static if ((is(K == class)) &&
191                keyEqualPred == `a is b`) // TODO: use better predicate compare?
192         alias StoreK = void*;
193     else static if (isPointer!K &&
194 					// TODO: use better predicate compare?
195 					(keyEqualPred == `a == b` ||
196 					 keyEqualPred == `a is b`))
197 		alias StoreK = void*;
198 	else
199 		alias StoreK = K;
200 
201 	/// Is `true` iff `this` is borrow-checked.
202     enum isBorrowChecked = borrowCheckFlag == BorrowCheckFlag.yes;
203 
204     /** In the hash map case, `V` is non-void, and a value is stored alongside
205      * the key of type `K`.
206      */
207     enum hasValue = !is(V == void);
208 
209     /** Is `true` iff `K` is an address, in which case holes/tombstones are represented by
210      * a specific value `holeKeyConstant`.
211      */
212     enum hasAddressLikeKey = (isAddress!K || isSlice!K);
213 
214     static if (hasAddressLikeKey)
215     {
216         enum hasHoleableKey = true;
217         enum holeKeyOffset = 0x1; // TODO: is this a good value? Or is 0xffff_ffff_ffff_ffff better?
218         @trusted enum holeKeyAddress = cast(void*)holeKeyOffset;
219 
220         /**
221          * See_Also: https://forum.dlang.org/post/p7726n$2apd$1@digitalmars.com
222          * TODO: test if ulong.max gives better performance
223          */
224         static K holeKeyConstant() @trusted pure nothrow @nogc
225         {
226             version(D_Coverage) {} else pragma(inline, true);
227             // TODO: note that cast(size_t*) will give address 0x8 instead of 0x1
228             static if (isSlice!K)
229             {
230                 alias E = typeof(K.init[0])*; // array element type
231                 auto ptr = cast(E)((cast(void*)null) + holeKeyOffset); // indicates a lazily deleted key
232                 return ptr[0 .. 0];
233             }
234             else
235                 return cast(K)((cast(void*)null) + holeKeyOffset); // indicates a lazily deleted key
236         }
237 
238         /** Returns: true iff `key` is a hole/tombstone key constant. */
239         static bool isHoleKeyConstant(const scope K key) @trusted pure nothrow @nogc
240         {
241             version(D_Coverage) {} else pragma(inline, true);
242             static if (isSlice!K) // for slices
243                 // suffice to compare pointer part
244                 return (key.ptr is holeKeyAddress);
245             else
246                 return (cast(const(void)*)key is holeKeyAddress);
247         }
248 
249         /** TODO: make these work
250          */
251         // enum K holeKey_1 = cast(K)((cast(size_t*)null));
252         // static immutable K holeKey_2 = cast(K)((cast(size_t*)null));
253     }
254     else static if (isHoleable!K)
255     {
256         enum hasHoleableKey = true;
257 		pragma(inline, true)
258         static K holeKeyConstant() @safe pure nothrow @nogc
259 			=> K.holeValue;
260         static bool isHoleKeyConstant(const scope K key) @safe pure nothrow @nogc
261         {
262             version(D_Coverage) {} else pragma(inline, true);
263             static if (__traits(hasMember, K, "isHole"))
264                 // typically faster by asserting value of member of aggregate `K`
265                 return key.isHole;
266             else
267                 return key is K.holeValue;
268         }
269     }
270     else static if (__traits(hasMember, K, "nullifier"))
271     {
272         alias Nullifier = typeof(K.init.nullifier);
273         // TODO: pragma(msg, K, " has nullifier ", Nullifier);
274         static if (isHoleable!Nullifier)
275         {
276             // TODO: pragma(msg, K, " has holeable nullifier ", Nullifier);
277             enum hasHoleableKey = true;
278             static K holeKeyConstant() @trusted pure nothrow @nogc
279             {
280                 K k;
281                 k.nullifier = Nullifier.holeValue;
282                 return k;
283             }
284             static bool isHoleKeyConstant(const scope K key) @trusted pure nothrow @nogc
285             	=> key.nullfier == Nullifier.holeValue;
286         }
287         else
288         {
289             enum hasHoleableKey = false;
290             // pragma(msg, "Need explicit hole/tombstone bitset for non-address-like key: ", K);
291             import core.bitop : bts, bt, btr;
292             import nxt.array_help : makeUninitializedBitArray, makeZeroedBitArray, makeReallocatedBitArrayZeroPadded;
293         }
294     }
295     else
296     {
297         enum hasHoleableKey = false;
298         // pragma(msg, "Need explicit hole/tombstone bitset for non-address-like key: ", K);
299         import core.bitop : bts, bt, btr;
300         import nxt.array_help : makeUninitializedBitArray, makeZeroedBitArray, makeReallocatedBitArrayZeroPadded;
301     }
302 
303     /// Element type.
304     static if (hasValue)
305     {
306         /** Map insertion status.
307          */
308         enum InsertionStatus
309         {
310             added,              ///< Element was added.
311             modified,           ///< Value of element was changed (map only).
312             unmodified          ///< Element was left unchanged.
313         }
314 
315         /// Mutable element reference with mutable constant key and value.
316         struct T
317         {
318             K key;
319             V value;
320         }
321 
322         /// Get key part of element.
323 		pragma(inline, true) static     inout(K) keyOf(SomeElement)(    scope        inout(SomeElement) element) => element.key;
324 		pragma(inline, true) static ref inout(K) keyOf(SomeElement)(ref scope return inout(SomeElement) element) => element.key;
325 
326         /// Get value part of element.
327         pragma(inline, true) static     inout(V) valueOf()(    scope        inout(T) element) => element.value;
328         pragma(inline, true) static ref inout(V) valueOf()(ref scope return inout(T) element) => element.value;
329 
330         /** Type of key stored. */
331         public alias KeyType = K;
332 
333         /** Type of value stored. */
334         public alias ValueType = V;
335 
336         enum nullKeyElement = T(defaultNullKeyConstantOf!K, V.init);
337 
338         /// Key-value element reference with head-const for `class` keys and mutable value.
339         static private struct KeyValueType
340         {
341             static if (isAddress!K) // for reference types
342             {
343                 K _key;          // no const because
344                 /** Key access is head-const. */
345                 inout(K) key() @property inout @safe pure nothrow @nogc => _key;
346             }
347             else
348                 const K key;
349             V value;
350         }
351 
352         /// Get key part.
353 		pragma(inline, true)
354         static auto ref inout(K) keyOf()(auto ref return scope inout(KeyValueType) element) @trusted
355 			=> cast(typeof(return))element.key; // needed for case: `inout(const(K)) => inout(K)`
356     }
357     else                        // HashSet
358     {
359         /** Set insertion status.
360          */
361         enum InsertionStatus
362         {
363             added,              ///< Element was added.
364             unmodified          ///< Element was left unchanged.
365         }
366 
367         alias T = K;            // short name for element type
368 
369         /// Get key part of element.
370 		pragma(inline, true)
371         static auto ref inout(SomeElement) keyOf(SomeElement)(auto ref return inout(SomeElement) element)
372 			=> element;
373 
374         enum nullKeyElement = defaultNullKeyConstantOf!K;
375     }
376 
377     /** TODO remove when https://github.com/dlang/dmd/pull/11000 has been merged
378      *
379      * @kinke: "The builtin `__argTypes` is (currently) only used/populated for Posix x64
380      * (and AArch64 for LDC), and not used by GDC at all AFAIK".
381      *
382      * See_Also: https://github.com/dlang/dmd/pull/11000#issuecomment-671103778
383      */
384     enum passElementByValue = (__traits(isCopyable, T) && // TODO: use isPOD instead?
385                                is(T U == __argTypes) &&
386                                U.length >= 1);
387 
388     /** Is `true` if an instance of `SomeKey` that can be implictly cast to `K`.
389      *
390      * For instance `const(char)[]` can be `@trusted`ly cast to `string` in a
391      * temporary scope.
392      */
393     template isScopedKeyType(SomeKey)
394     {
395         static if (is(SomeKey == class))
396             enum isScopedKeyType = (is(const(SomeKey) : const(K)));
397         else
398             enum isScopedKeyType = (is(K : SomeKey) || // `K is` implicitly convertible from `SomeKey`
399                                     is(SomeKey : U[], U) && // is array
400                                     is(typeof(K(SomeKey.init))));
401     }
402 
403     alias ElementType = T;
404 
405     /** Make with room for storing at least `minimumCapacity` number of elements.
406      *
407      * See_Also:
408      * https://forum.dlang.org/post/nyngzsaeqxzzuumivtze@forum.dlang.org
409      */
410     static typeof(this) withCapacity()(in size_t minimumCapacity) /* template-lazy */
411     {
412         static if (usePrimeCapacity)
413         {
414             PrimeIndex primeIndex;
415             immutable initialCapacity = minimumCapacity == 0 ? 1 : ceilingPrime(minimumCapacity + 1, primeIndex);
416             assert(minimumCapacity < initialCapacity); // need at least one vacancy
417             return typeof(return)(makeDefaultInitializedStoreOfCapacity(initialCapacity), primeIndex, 0);
418         }
419         else
420         {
421             immutable initialCapacity = minimumCapacity == 0 ? 1 : nextPow2(minimumCapacity);
422             assert(minimumCapacity < initialCapacity); // need at least one vacancy
423             return typeof(return)(makeDefaultInitializedStoreOfCapacity(initialCapacity), 0);
424         }
425     }
426 
427     /** Make default-initialized store with `capacity` number of slots.
428      */
429     static private T[] makeDefaultInitializedStoreOfCapacity()(in size_t capacity) @trusted pure nothrow @nogc /* template-lazy */
430     {
431         static if (usePrimeCapacity)
432         {
433             // TODO: check that capacity is prime?
434         }
435         else
436         {
437             debug import std.math : isPowerOf2;
438             debug assert(isPowerOf2(capacity)); // quadratic probing needs power of two capacity (`_store.length`)
439         }
440 
441         // TODO: cannot use makeArray here because it cannot handle uncopyable types
442         // import std.experimental.allocator : makeArray;
443         // auto store = allocator.makeArray!T(capacity, nullKeyElement);
444 
445         import nxt.bit_traits : isAllZeroBits;
446 
447         immutable byteCount = T.sizeof*capacity;
448 
449         static if (hasAddressLikeKey ||
450                    (__traits(isZeroInit, K)  &&
451                     __traits(hasMember, K, "nullifier")) ||
452                    // TODO: add check for __traits(isZeroInit, K) and member `K.nullValue` == `K.init`
453                    (__traits(hasMember, K, `nullValue`) && // if key has a null value
454                     __traits(compiles, { enum _ = isAllZeroBits!(K, K.nullValue); }) && // prevent strange error given when `K` is `knet.data.Data`
455                     isAllZeroBits!(K, K.nullValue))) // check that it's zero bits only
456         {
457             // pragma(msg, "zero-allocate:", "K:", K, " V:", V);
458             // TODO: use std.experimental.allocator.makeArray instead of this which handles clever checking for isZeroInit
459             import nxt.container.traits : makeInitZeroArray;
460             auto store = makeInitZeroArray!(T, allocator)(capacity);
461             if (store.ptr is null &&
462 				capacity >= 1)
463                 onOutOfMemoryError();
464         }
465         else                    // when default null key is not represented by zeros
466         {
467             // pragma(msg, "emplace:", "K:", K, " V:", V);
468             auto store = cast(T[])allocator.allocate(byteCount);
469             if (store.ptr is null &&
470 				byteCount >= 1)
471                 onOutOfMemoryError();
472             foreach (ref bin; store)
473             {
474                 import core.lifetime : emplace;
475                 enum hasNullValueKey = __traits(hasMember, K, `nullValue`);
476                 static if (hasNullValueKey &&
477                            !is(typeof(emplace(&keyOf(bin), K.nullValue)))) // __traits(compiles) fails here when building knet
478                     pragma(msg, __FILE__, ":", __LINE__, ":warning: emplace fails for null-Value key type ", K);
479 
480                 // initialize key
481                 static if (hasNullValueKey &&
482                            is(typeof(emplace(&keyOf(bin), K.nullValue))))
483                     emplace(&keyOf(bin), K.nullValue); // initialize in-place with explicit `K.nullValue`
484                 else
485                 {
486                     emplace(&keyOf(bin)); // initialize in-place with default value
487                     keyOf(bin).nullify(); // moveEmplace doesn't init source of type `Nullable`
488                 }
489 
490                 // initialize value
491                 static if (hasValue)
492                 {
493                     static if (hasElaborateDestructor!V)
494                         emplace(&valueOf(bin)); // initialize in-place
495                     else static if (mustAddGCRange!V)
496                         valueOf(bin) = V.init;
497                     else {}	// ok for this case to have uninitialized value part
498                 }
499             }
500         }
501 
502         static if (mustAddGCRange!T)
503             gc_addRange(store.ptr, byteCount);
504 
505         return store;
506     }
507 
508     static private T[] allocateUninitializedStore()(in size_t capacity) @trusted pure nothrow @nogc /* template-lazy */
509     {
510         immutable byteCount = T.sizeof*capacity;
511         auto store = cast(typeof(return))allocator.allocate(byteCount);
512         static if (mustAddGCRange!T)
513             gc_addRange(store.ptr, byteCount);
514         if (store.ptr is null &&
515             byteCount >= 1)
516             onOutOfMemoryError();
517         return store;
518     }
519 
520     import std.range.primitives : StdElementType = ElementType;
521     import std.traits : isIterable, isAssignable;
522 
523     /** Make with the element `element`. */
524     this(T element)
525     {
526         static if (usePrimeCapacity)
527         {
528             _primeIndex = PrimeIndex.init;
529             _store = makeDefaultInitializedStoreOfCapacity(ceilingPrime(1 + 1, _primeIndex));
530         }
531         else
532             _store = makeDefaultInitializedStoreOfCapacity(nextPow2(1));
533         _count = 0;
534         static if (__traits(isPOD, T))
535             insertWithoutGrowthNoStatus(element);
536         else
537             insertWithoutGrowthNoStatus(move(element));
538     }
539 
540     static if (hasHoleableKey)
541     {
542         private this(T[] store, in size_t count)
543         {
544             _store = store;
545             _count = count;
546         }
547     }
548     else
549     {
550         private this(T[] store, in size_t count, size_t* holesPtr = null)
551         {
552             _store = store;
553             _count = count;
554             _holesPtr = holesPtr;
555         }
556     }
557 
558     /** Make with the elements `elements`. */
559     static typeof(this) withElements(R)(R elements)
560     if (isIterable!R &&
561         isAssignable!(T, StdElementType!R))
562     {
563         import std.range.primitives : hasLength;
564         static if (hasLength!R)
565         {
566             typeof(this) that = withCapacity(elements.length);
567             foreach (ref element; elements)
568                 that.insertWithoutGrowthNoStatus(element);
569         }
570         else
571         {
572             typeof(this) that;
573             foreach (ref element; elements)
574                 that.insert(element);
575         }
576         return that;
577     }
578 
579     /// Destruct.
580     ~this() nothrow @nogc
581     {
582         release();
583     }
584 
585     /// No copying.
586     @disable this(this);
587 
588     /// Returns: a shallow duplicate of `this`.
589     typeof(this) dup()() const @trusted /* template-lazy */
590     {
591         T[] storeCopy = allocateUninitializedStore(_store.length); // unsafe
592 
593         foreach (immutable i, ref bin; _store)
594             if (isOccupiedAtIndex(i)) // normal case
595             {
596                 static if (hasValue) // map
597                 {
598                     duplicateEmplace(bin.key, storeCopy[i].key);
599                     duplicateEmplace(bin.value, storeCopy[i].value);
600                 }
601                 else            // set
602                     duplicateEmplace(bin, storeCopy[i]);
603             }
604             else
605             {
606                 import core.lifetime : emplace;
607                 emplace(&storeCopy[i]); // TODO: only emplace key and not value
608                 keyOf(storeCopy[i]).nullify();
609             }
610 
611         static if (!hasHoleableKey)
612             if (_holesPtr)
613             {
614                 immutable wordCount = holesWordCount(_store.length);
615                 auto holesPtrCopy = makeUninitializedBitArray!Allocator(_store.length);
616                 holesPtrCopy[0 .. wordCount] = _holesPtr[0 .. wordCount]; // TODO: use memcpy instead?
617 
618                 return typeof(return)(storeCopy, _count, holesPtrCopy);
619             }
620 
621         return typeof(return)(storeCopy, _count);
622     }
623 
624     /// Equality.
625     bool opEquals()(const scope auto ref typeof(this) rhs) const
626     {
627         if (_count != rhs._count)
628             return false;       // quick discardal
629 
630         foreach (immutable i, const ref bin; _store)
631             if (isOccupiedAtIndex(i))
632             {
633                 static if (hasValue)
634                 {
635                     auto valuePtr = bin.key in rhs;
636                     if (!valuePtr)
637                         return false;
638                     // TODO: make != a parameter that can also be typically !is. TODO: ask forum about this
639                     if ((*valuePtr) != bin.value)
640                         return false;
641                 }
642                 else
643                     if (!rhs.contains(bin))
644                         return false;
645             }
646 
647         return true;
648     }
649 
650     static if (true)
651     {
652     private:
653 
654         static if (!hasHoleableKey)
655         {
656 			/// Deallocate holes/tombstones.
657             void deallocateHoles() @trusted
658             {
659                 if (_holesPtr)
660                 {
661                     static if (__traits(hasMember, Allocator, "deallocatePtr"))
662                         allocator.deallocatePtr(_holesPtr);
663                     else
664                         allocator.deallocate(_holesPtr[0 .. holesWordCount(_store.length)]);
665                 }
666             }
667 
668 			/// Clear holes/tombstones.
669             static size_t* reallocateHoles(size_t[] holes, in size_t byteCount) @trusted
670             {
671                 auto rawHoles = cast(void[])holes;
672                 immutable ok = allocator.reallocate(rawHoles, byteCount);
673                 assert(ok, "couldn't reallocate holes");
674                 return cast(typeof(return))rawHoles.ptr;
675             }
676 
677 			/// Clear holes/tombstones.
678             void clearHoles()
679             {
680                 deallocateHoles();
681                 _holesPtr = null;
682             }
683 
684 			/// Number of bytes in a word.
685 			enum wordBytes = size_t.sizeof;
686 
687 			/// Number of bits in a word.
688 			enum wordBits = 8*wordBytes;
689 
690             /// Returns: number of words (`size_t`) needed to represent `binCount` holes.
691 			pragma(inline, true)
692             static size_t holesWordCount(in size_t binCount)
693 				=> (binCount / wordBits +
694 					(binCount % wordBits ? 1 : 0));
695 
696 			pragma(inline, true)
697             static size_t binBlockBytes(in size_t binCount) => wordBytes*holesWordCount(binCount);
698 
699 			/// Untag hole/tombstone at index `index`.
700             void untagHoleAtIndex(in size_t index) @trusted
701             {
702                 pragma(inline, true);
703                 version(unittest) assert(index < _store.length);
704                 if (_holesPtr !is null)
705                     btr(_holesPtr, index);
706             }
707 
708 			pragma(inline, true)
709             static bool hasHoleAtPtrIndex(const scope size_t* holesPtr, in size_t index) @trusted
710             	=> (holesPtr &&
711 					bt(holesPtr, index) != 0);
712         }
713 
714 		/// Tag hole/tombstone at index `index`.
715         void tagHoleAtIndex(in size_t index) @trusted
716         {
717             pragma(inline, true);
718             version(unittest) assert(index < _store.length);
719             static if (hasHoleableKey)
720                 keyOf(_store[index]) = holeKeyConstant;
721             else
722             {
723                 if (_holesPtr is null) // lazy allocation
724                     _holesPtr = makeZeroedBitArray!Allocator(_store.length);
725                 bts(_holesPtr, index);
726             }
727         }
728     }
729 
730     static if (isBorrowChecked)
731         static immutable borrowedErrorMessage = "cannot mutate this when it's borrowed";
732 
733     /// Empty.
734     void clear()
735     {
736         static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
737         release();
738 		// TODO: functionize?:
739         _store = typeof(_store).init;
740         static if (usePrimeCapacity)
741             _primeIndex = 0;
742         static if (!hasHoleableKey)
743             _holesPtr = null;
744         _count = 0;
745     }
746 
747     /// Release internal allocations.
748     private void release() scope
749     {
750         releaseBinElements();
751         releaseStoreAndHolesSlices();
752     }
753 
754     /// Release bin elements.
755     private void releaseBinElements() scope
756     {
757         foreach (ref bin; _store)
758         {
759             static if (hasElaborateDestructor!T)
760                 .destroy(bin);
761             else static if (mustAddGCRange!T)
762                 bin = T.init;
763         }
764     }
765 
766     /// Release store slice and holes.
767     private void releaseStoreAndHolesSlices() scope
768     {
769         releaseStoreSlice(_store);
770         static if (!hasHoleableKey)
771             deallocateHoles();
772     }
773 
774     /// Release store slice.
775     static private void releaseStoreSlice(T[] store) @trusted
776     {
777         if (store.ptr is null) { return; } // `gc_removeRange` fails for null input
778         static if (mustAddGCRange!T)
779             gc_removeRange(store.ptr); // `gc_removeRange` fails for null input
780         static if (__traits(hasMember, Allocator, "deallocatePtr"))
781             allocator.deallocatePtr(store.ptr);
782         else
783             allocator.deallocate(store);
784     }
785 
786     /// Adjust `key`.
787     private auto adjustKey(SomeKey)(const return scope SomeKey key) const scope @trusted
788     {
789         pragma(inline, true);            // must be inlined
790         static if (is(SomeKey : U[], U)) // is array (slice)
791             /* because return value is used only temporarily it's ok to cast to
792              * `immutable` to prevent GC-allocations in types such as
793              * `sso_string.SSOString` */
794             return cast(immutable(typeof(key[0]))[])key;
795         else
796             return key;
797     }
798 
799     /** Check if `element` is stored.
800      *
801      * Parameter `key` may be non-immutable, for instance const(char)[]
802      * eventhough key type `K` is `string`.
803      *
804      * Returns: `true` if element is present, `false` otherwise.
805      */
806     bool contains(SomeKey)(const scope SomeKey key) const scope @trusted /* `auto ref` here makes things slow */
807     if (isScopedKeyType!(typeof(key)))
808 	in(!key.isNull)
809     {
810         // pragma(msg, SomeKey.stringof ~ " " ~ K.stringof, " ", is(K : SomeKey), " ", is(SomeKey : K));
811         // debug static assert(isScopedKeyType!(typeof(key)), SomeKey.stringof ~ " " ~ K.stringof);
812         version(LDC) pragma(inline, true);
813 
814         static if (hasHoleableKey) { assert(!isHoleKeyConstant(cast(const(K))adjustKey(key))); }
815 
816         static if (linearSearchMaxSize != 0)
817             if (_store.length * T.sizeof <= linearSearchMaxSize)
818                 return containsUsingLinearSearch(key);
819 
820         immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKey(key)); // cast scoped `key` is @trusted
821         return (hitIndex != _store.length &&
822                 isOccupiedAtIndex(hitIndex));
823     }
824 
825     /** Check if `element` is stored.
826      *
827      * Uses linear search instead of hashing plus probing and may be faster for
828      * for small tables with complicated hash functions.
829      *
830      * Parameter `key` may be non-immutable, for instance const(char)[]
831      * eventhough key type `K` is `string`.
832      *
833      * Returns: `true` if element is present, `false` otherwise.
834      */
835     bool containsUsingLinearSearch(SomeKey)(const scope SomeKey key) const scope @trusted /* template-lazy, `auto ref` here makes things slow */
836     if (isScopedKeyType!(typeof(key)))
837     in(!key.isNull)
838     {
839         static if (hasHoleableKey) { assert(!isHoleKeyConstant(cast(const(K))adjustKey(key))); }
840         static if (is(SomeKey == Nullable!(_), _))
841         {
842             import std.algorithm.searching : canFind;
843             import std.traits : TemplateArgsOf;
844             alias args = TemplateArgsOf!(SomeKey);
845             debug static assert(args.length == 2,
846                           "linear search for Nullable without nullValue is slower than default `this.contains()` and is not allowed");
847             alias UnderlyingType = args[0];
848             return length >= 1 && (cast(UnderlyingType[])_store).canFind!keyEqualPredFn(key.get());
849         }
850         else
851         {
852             foreach (const ref bin; _store)
853                 if (keyEqualPredFn(keyOf(bin), key))
854                     return true;
855             return false;
856         }
857     }
858 
859     /** Check if `element` is stored. Move found element to a hole if possible.
860         Returns: `true` if element is present, `false` otherwise.
861     */
862     bool containsWithHoleMoving()(const scope K key) /* template-lazy, `auto ref` here makes things slow */
863     in(!key.isNull)
864     {
865         version(LDC) pragma(inline, true);
866 
867         static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); }
868         static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
869 
870         immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key));
871         // TODO: update holes
872         return (hitIndex != _store.length &&
873                 isOccupiedAtIndex(hitIndex));
874     }
875 
876     /** Insert `element`, being either a key-value (map-case) or a just a key
877      * (set-case).
878      *
879      * If `element` is a nullable type and it is null an `AssertError` is thrown.
880      */
881     InsertionStatus insert()(const T element) @trusted /* template-lazy. need `T` to be `const` in `class` case */
882 	in(!keyOf(element).isNull)
883     {
884         version(LDC) pragma(inline, true);
885 
886         static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(keyOf(element))); }
887         static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
888 
889         reserveExtra(1);
890         size_t hitIndex = 0;
891         static if (__traits(isPOD, T))
892             return insertWithoutGrowth(element, hitIndex);
893         else
894             return insertWithoutGrowth(move(*cast(T*)&element), hitIndex);
895     }
896 
897     /** Insert `element`, being either a key-value (map-case) or a just a key
898      * (set-case).
899      *
900      * If `element` is a nullable type and it is null an `AssertError` is thrown.
901      *
902      * Returns: reference to existing element if present, otherwise new `element`.
903      *
904      * Can be used for implementing, for instance, caching of typically strings.
905      */
906     ref T insertAndReturnElement(SomeElement)(scope SomeElement element) return /* template-lazy */
907     in(!keyOf(element).isNull)
908     {
909         version(LDC) pragma(inline, true);
910 
911         static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(cast(K)adjustKey(keyOf(element)))); }
912         static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
913 
914         reserveExtra(1);
915         static if (__traits(isPOD, SomeElement))
916             immutable hitIndex = insertWithoutGrowthNoStatus(element);
917         else
918             immutable hitIndex = insertWithoutGrowthNoStatus(move(element));
919         return _store[hitIndex];
920     }
921 
922     /** Insert `elements`, all being either a key-value (map-case) or a just a key (set-case).
923      */
924     void insertN(R)(R elements) @trusted
925     if (isIterable!R &&
926         __traits(isCopyable, T))           // TODO: support uncopyable T?
927     {
928         static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
929         import std.range.primitives : hasLength;
930         static if (hasLength!R)
931             reserveExtra(elements.length); // might create unused space in `_store` store
932         foreach (ref element; elements)
933         {
934             static if (!hasLength!R)
935                 reserveExtra(1);
936             static if (hasIndirections!T)
937                 insertWithoutGrowthNoStatus(element);
938             else
939                 insertWithoutGrowthNoStatus(*cast(Unqual!T*)&element);
940         }
941     }
942 
943     /// Is `true` iff in-place rehashing during growth should be performed.
944     enum bool growInPlaceFlag = false; // TODO: warning `growInPlaceWithCapacity` is buggy
945 
946     /// Numerator for grow scale.
947     enum growScaleP = 3;
948     /// Denominator for grow scale.
949     enum growScaleQ = 2;
950 
951     /** Reserve rom for `extraCapacity` number of extra buckets. */
952     void reserveExtra(in size_t extraCapacity) // not template-lazy
953     {
954         version(LDC) pragma(inline, true);
955         static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
956         immutable newCapacity = (_count + extraCapacity)*growScaleP/growScaleQ;
957         if (newCapacity > _store.length)
958             growWithNewCapacity(newCapacity);
959     }
960 
961     /// Grow (rehash) to make for `newCapacity` number of elements.
962     private void growWithNewCapacity()(in size_t newCapacity) /* template-lazy */
963     {
964         version(unittest) assert(newCapacity > _store.length);
965         static if (__traits(hasMember, Allocator, "reallocate"))
966         {
967             static if (growInPlaceFlag)
968                 growInPlaceWithCapacity(newCapacity);
969             else
970                 growStandardWithNewCapacity(newCapacity);
971         }
972         else
973             growStandardWithNewCapacity(newCapacity);
974     }
975 
976     /// Grow (rehash) store to make room for `newCapacity` number of elements.
977     private void growStandardWithNewCapacity()(in size_t newCapacity) /* template-lazy */
978     {
979         version(unittest) assert(newCapacity > _store.length);
980         auto next = typeof(this).withCapacity(newCapacity);
981         foreach (immutable i, ref bin; _store)
982             if (isOccupiedAtIndex(i))
983             {
984                 next.insertMoveWithoutGrowth(bin); // value is zeroed but
985                 static if (!hasHoleableKey)
986                     keyOf(bin).nullify(); // keyC must zeroed
987             }
988         move(next, this);
989     }
990 
991 	/// Tag as lazily delete element at index `index`.`
992     private void tagAsLazilyDeletedElementAtIndex(in size_t index)
993     {
994         version(LDC) pragma(inline, true);
995 
996         // key
997         static if (linearSearchMaxSize != 0)
998             if (_store.length * T.sizeof <= linearSearchMaxSize)
999             {
1000                 keyOf(_store[index]).nullify();
1001                 goto done;
1002             }
1003 
1004         static if (hasHoleableKey)
1005             keyOf(_store[index]) = holeKeyConstant;
1006         else
1007         {
1008             keyOf(_store[index]).nullify();
1009             tagHoleAtIndex(index);
1010         }
1011 
1012     done:
1013 
1014         // value
1015         static if (hasValue)
1016         {
1017             static if (hasElaborateDestructor!V) // if we should clear all
1018                 .destroy(valueOf(_store[index]));
1019             static if (mustAddGCRange!V) // if we should clear all
1020                 valueOf(_store[index]) = V.init; // avoid GC mark-phase dereference
1021         }
1022     }
1023 
1024 	/// Insert `element` at `index`.
1025     private void insertElementAtIndex(SomeElement)(scope SomeElement element, in size_t index) @trusted /* template-lazy */
1026     {
1027         version(LDC) pragma(inline, true);
1028         static if (isSlice!SomeElement &&
1029                    !is(typeof(SomeElement.init[0]) == immutable))
1030         {
1031             /* key is an array of non-`immutable` elements which cannot safely
1032              * be stored because keys must be immutable for hashing to work
1033              * properly, therefore duplicate */
1034             keyOf(_store[index]) = element.idup;
1035         }
1036         else
1037         {
1038             static if (__traits(isPOD, SomeElement))
1039                 _store[index] = element;
1040             else
1041             {
1042                 static if (__traits(isPOD, K))
1043                     keyOf(_store[index]) = keyOf(element);
1044                 else
1045                     move(keyOf(element),
1046                          keyOf(_store[index]));
1047 
1048                 static if (hasValue)
1049                 {
1050                     import core.lifetime : moveEmplace;
1051                     moveEmplace(valueOf(element),
1052                                 valueOf(_store[index]));
1053                 }
1054             }
1055         }
1056     }
1057 
1058     /// Rehash elements in-place.
1059     private void rehashInPlace()() @trusted /* template-lazy */
1060     {
1061         import core.bitop : bts, bt;
1062         import nxt.array_help : makeZeroedBitArray, wordCountOfBitCount;
1063 
1064         size_t* dones = makeZeroedBitArray!Allocator(_store.length);
1065 
1066         foreach (immutable doneIndex; 0 .. _store.length)
1067         {
1068             if (bt(dones, doneIndex)) { continue; } // if _store[doneIndex] continue
1069             if (isOccupiedAtIndex(doneIndex))
1070             {
1071                 import core.lifetime : moveEmplace;
1072                 T currentElement = void;
1073 
1074                 // TODO: functionize:
1075                 moveEmplace(_store[doneIndex], currentElement);
1076                 static if (is(K == Nullable!(_), _))
1077                     keyOf(_store[doneIndex]).nullify(); // `moveEmplace` doesn't init source of type Nullable
1078 
1079                 while (true)
1080                 {
1081                     // TODO remove param `element`
1082                     static if (passElementByValue)
1083                         alias pred = (immutable scope index,
1084                                       const scope element) => (!isOccupiedAtIndex(index) || // free slot
1085                                                                !bt(dones, index)); // or a not yet replaced element
1086                     else
1087                         alias pred = (immutable scope index,
1088                                       const scope ref element) => (!isOccupiedAtIndex(index) || // free slot
1089                                                                    !bt(dones, index)); // or a not yet replaced element
1090                     static if (usePrimeCapacity)
1091                         immutable hitIndex = xxx;
1092                     else
1093                         immutable hitIndex = _store[].triangularProbeFromIndex!(pred, assumeNonFullHaystack)(keyToIndex(keyOf(currentElement)));
1094                     assert(hitIndex != _store.length, "no free slot");
1095 
1096                     bts(dones, hitIndex); // _store[hitIndex] will be at it's correct position
1097 
1098                     if (isOccupiedAtIndex(doneIndex))
1099                     {
1100                         T nextElement = void;
1101 
1102                         // TODO: functionize:
1103                         moveEmplace(_store[hitIndex], nextElement); // save non-free slot
1104                         static if (is(K == Nullable!(_), _))
1105                             keyOf(_store[hitIndex]).nullify(); // `moveEmplace` doesn't init source of type Nullable
1106 
1107                         moveEmplace(currentElement, _store[hitIndex]);
1108                         moveEmplace(nextElement, currentElement);
1109                     }
1110                     else // if no free slot
1111                     {
1112                         moveEmplace(currentElement, _store[hitIndex]);
1113                         break; // inner iteration is finished
1114                     }
1115                 }
1116             }
1117             bts(dones, doneIndex); // _store[doneIndex] is at it's correct position
1118         }
1119 
1120         allocator.deallocate(cast(void[])(dones[0 .. wordCountOfBitCount(_store.length)]));
1121 
1122         static if (!hasHoleableKey)
1123             clearHoles();
1124     }
1125 
1126     /** Grow (with rehash) store in-place making room for `minimumCapacity` number of elements. */
1127     private void growInPlaceWithCapacity()(in size_t minimumCapacity) @trusted /* template-lazy */
1128     {
1129         assert(minimumCapacity > _store.length);
1130 
1131         static if (usePrimeCapacity)
1132             immutable newCapacity = ceilingPrime(minimumCapacity, _primeIndex);
1133         else
1134             immutable newCapacity = nextPow2(minimumCapacity);
1135 
1136         immutable newByteCount = T.sizeof*newCapacity;
1137 
1138         const oldStorePtr = _store.ptr;
1139         immutable oldLength = _store.length;
1140 
1141         auto rawStore = cast(void[])_store;
1142         if (allocator.reallocate(rawStore, newByteCount))
1143         {
1144             _store = cast(T[])rawStore;
1145             static if (mustAddGCRange!T)
1146             {
1147                 if (oldStorePtr !is null)
1148                     gc_removeRange(oldStorePtr); // `gc_removeRange` fails for null input
1149                 gc_addRange(_store.ptr, newByteCount);
1150             }
1151 
1152             static if (!hasHoleableKey)
1153                 if (_holesPtr)
1154                     _holesPtr = makeReallocatedBitArrayZeroPadded!Allocator(_holesPtr,
1155                                                                             oldLength,
1156                                                                             _store.length);
1157 
1158             // TODO: make this an array operation `nullifyAll` or `nullifyN`
1159             foreach (ref bin; _store[oldLength .. newCapacity])
1160                 keyOf(bin).nullify(); // move this `init` to reallocate() above?
1161 
1162             rehashInPlace();
1163         }
1164         else
1165             assert(0, "couldn't reallocate bin");
1166     }
1167 
1168     /** Insert (without growth) `element` at `hitIndex`. */
1169     private InsertionStatus insertWithoutGrowth(SomeElement)(const scope SomeElement element, /* template-lazy */
1170                                                              out size_t hitIndex) @trusted
1171     {
1172         version(LDC) pragma(inline, true);
1173         version(unittest)
1174         {
1175             assert(!keyOf(element).isNull);
1176             static if (hasHoleableKey) { assert(!isHoleKeyConstant(adjustKey(keyOf(element)))); }
1177         }
1178 
1179         size_t holeIndex = size_t.max; // first hole index to written to if hole found
1180         immutable hitIndexPrel = indexOfKeyOrVacancyAndFirstHole(keyOf(element), holeIndex);
1181         if (hitIndexPrel == _store.length || // keys miss and holes may have filled all empty slots
1182             keyOf(_store[hitIndexPrel]).isNull) // just key miss but a hole may have been found on the way
1183         {
1184             immutable hasHole = holeIndex != size_t.max; // hole was found along the way
1185 
1186             if (hasHole)
1187                 hitIndex = holeIndex; // pick hole instead
1188             else
1189                 hitIndex = hitIndexPrel; // normal hit
1190 
1191             version(unittest) assert(hitIndex != _store.length, "no null or hole slot");
1192 
1193             static if (__traits(isPOD, SomeElement))
1194                 insertElementAtIndex(*cast(SomeElement*)&element, hitIndex);
1195             else
1196                 insertElementAtIndex(move(*cast(SomeElement*)&element), hitIndex);
1197 
1198             static if (!hasHoleableKey)
1199                 if (hasHole)
1200                     untagHoleAtIndex(hitIndex);
1201 
1202             _count = _count + 1;
1203             return InsertionStatus.added;
1204         }
1205         else
1206             hitIndex = hitIndexPrel;
1207 
1208         static if (hasValue)
1209         {
1210             static if (__traits(isStaticArray, V))
1211                 // identity comparison of static arrays implicitly coerces them
1212                 // to slices, which are compared by reference, so don't use !is here
1213                 immutable valueDiffers = (valueOf(element) !=
1214                                           valueOf(_store[hitIndexPrel])); // only value changed
1215             else
1216                 immutable valueDiffers = (valueOf(element) !is
1217                                           valueOf(_store[hitIndexPrel])); // only value changed
1218 
1219             if (valueDiffers) // only value changed
1220             {
1221                 move(valueOf(*cast(SomeElement*)&element),
1222                      valueOf(_store[hitIndexPrel])); // value is defined so overwrite it
1223                 return InsertionStatus.modified;
1224             }
1225         }
1226         return InsertionStatus.unmodified;
1227     }
1228 
1229     /** Insert (without growth) `element` and return index to bin where insertion happended. */
1230     private size_t insertWithoutGrowthNoStatus(SomeElement)(const scope SomeElement element) @trusted /* template-lazy */
1231     {
1232         version(LDC) pragma(inline, true);
1233         version(unittest)
1234         {
1235             assert(!keyOf(element).isNull);
1236             static if (hasHoleableKey) { assert(!isHoleKeyConstant(adjustKey(keyOf(element)))); }
1237         }
1238 
1239         size_t hitIndex = 0;
1240         size_t holeIndex = size_t.max; // first hole index to written to if hole found
1241         immutable hitIndexPrel = indexOfKeyOrVacancyAndFirstHole(adjustKey(keyOf(element)), holeIndex);
1242         if (hitIndexPrel == _store.length || // keys miss and holes may have filled all empty slots
1243             keyOf(_store[hitIndexPrel]).isNull) // just key miss but a hole may have been found on the way
1244         {
1245             immutable hasHole = holeIndex != size_t.max; // hole was found along the way
1246             if (hasHole)
1247                 hitIndex = holeIndex; // pick hole instead
1248             else
1249                 hitIndex = hitIndexPrel; // normal hit
1250 
1251             version(unittest) assert(hitIndex != _store.length, "no null or hole slot");
1252 
1253             static if (__traits(isPOD, SomeElement))
1254                 insertElementAtIndex(*cast(SomeElement*)&element, hitIndex);
1255             else
1256                 insertElementAtIndex(move(*cast(SomeElement*)&element), hitIndex);
1257 
1258             static if (!hasHoleableKey)
1259                 if (hasHole) { untagHoleAtIndex(hitIndex); }
1260 
1261             _count = _count + 1;
1262             return hitIndex;
1263         }
1264         else
1265             hitIndex = hitIndexPrel;
1266 
1267         static if (hasValue)
1268             // modify existing value
1269             move(valueOf(*cast(SomeElement*)&element),
1270                  valueOf(_store[hitIndexPrel])); // value is defined so overwrite it
1271 
1272         return hitIndex;
1273     }
1274 
1275     /** Insert `element`, being either a key-value (map-case) or a just a key (set-case).
1276      */
1277     private InsertionStatus insertMoveWithoutGrowth()(ref T element) /* template-lazy */
1278     {
1279         version(LDC) pragma(inline, true);
1280         size_t hitIndex = 0;
1281         return insertWithoutGrowth(move(element), hitIndex);
1282     }
1283 
1284     static if (hasValue)
1285     {
1286         /** Insert or replace `value` at `key`. */
1287         InsertionStatus insert()(K key, V value) /* template-lazy */
1288         {
1289             pragma(inline, true); // LDC must have this
1290             static if (__traits(isPOD, K))
1291             {
1292                 static if (__traits(isPOD, V))
1293                     return insert(T(key, value));
1294                 else
1295                     return insert(T(key, move(value)));
1296             }
1297             else
1298             {
1299                 static if (__traits(isPOD, V))
1300                     return insert(T(move(key), value));
1301                 else
1302                     return insert(T(move(key), move(value)));
1303             }
1304         }
1305     }
1306 
1307     static if (!hasValue)       // HashSet
1308     {
1309         scope const(K)* opBinaryRight(string op, SomeKey)(const scope SomeKey key) const return @trusted
1310         if (op == `in` &&
1311             isScopedKeyType!(typeof(key)))
1312         in(!key.isNull)
1313         {
1314             version(D_Coverage) {} else pragma(inline, true);
1315             static if (hasHoleableKey) { assert(!isHoleKeyConstant(cast(K)adjustKey(key))); }
1316             immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted
1317 			immutable match = hitIndex != _store.length && isOccupiedAtIndex(hitIndex);
1318             if (match)
1319                 return &_store[hitIndex];
1320             else
1321                 return null;
1322         }
1323 
1324         ref typeof(this) opOpAssign(string op, SomeKey)(const scope SomeKey key) return @trusted
1325         if (op == `~` &&        // binary assignment operator `~=`
1326             isScopedKeyType!(typeof(key)))
1327         {
1328             version(LDC) pragma(inline, true);
1329             reserveExtra(1);
1330             immutable hitIndex = insertWithoutGrowthNoStatus(key);
1331             return this;
1332         }
1333 
1334         /** Try to retrieve `class`-element of type `Class` constructed with
1335          * parameters `params`.
1336          *
1337          * Typically used to implement (polymorphic) caching of class-types
1338          * without the need for GG-allocating a temporary instance of a
1339          * `class`-element potentially already stored in `this` set.
1340          *
1341          * Polymorphic caching can be realized by setting `hasher` to
1342          * `hash_functions.hashOfPolymorphic`.
1343          */
1344         scope const(Class) tryGetElementFromCtorParams(Class, Params...)(scope Params params) const return @trusted
1345         if (is(Class : K))
1346         {
1347             import core.lifetime : emplace;
1348             void[__traits(classInstanceSize, Class)] tempNode_ = void;
1349             scope Class temp = emplace!(Class)(tempNode_, params);
1350             Class* hit = cast(Class*)(temp in this);
1351 
1352             static if (__traits(hasMember, Class, "__dtor"))
1353                 temp.__dtor();
1354 
1355             if (hit)
1356             {
1357                 auto typedHit = cast(typeof(return))*hit;
1358                 assert(typedHit, "Expected class " ~ Class.stringof ~ " but got hit was of other type"); // TODO: give warning or throw
1359                 return typedHit;
1360             }
1361             return null;
1362         }
1363     }
1364 
1365     static if (hasValue)        // HashMap
1366     {
1367         scope inout(V)* opBinaryRight(string op, SomeKey)(const scope SomeKey key) inout return @trusted // `auto ref` here makes things slow
1368         if (op == `in` &&
1369             isScopedKeyType!(SomeKey))
1370         {
1371             version(LDC) pragma(inline, true);
1372             // pragma(msg, SomeKey, " => ", K);
1373             immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKey(key)); // cast scoped `key` is @trusted
1374 			immutable match = hitIndex != _store.length && isOccupiedAtIndex(hitIndex);
1375             if (match)
1376                 return cast(typeof(return))&_store[hitIndex].value;
1377             else
1378                 return null;
1379         }
1380 
1381         /// Indexing.
1382         scope ref inout(V) opIndex(SomeKey)(const scope SomeKey key) inout return @trusted // `auto ref` here makes things slow.
1383         if (isScopedKeyType!(typeof(key)))
1384         {
1385             import core.exception : onRangeError;
1386             version(LDC) pragma(inline, true);
1387             immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted
1388 			immutable match = hitIndex != _store.length && isOccupiedAtIndex(hitIndex);
1389             if (!match)
1390 				onRangeError();
1391             return _store[hitIndex].value;
1392         }
1393 
1394         /** Get value of `key` or `defaultValue` if `key` not present (and
1395          * therefore `nothrow`).
1396          *
1397          * Returns: value reference iff `defaultValue` is an l-value.
1398          *
1399          * TODO: make `defaultValue` `lazy` when that can be `nothrow`
1400          */
1401         auto ref inout(V) get()(const scope K key, /* template-lazy */
1402                                 auto ref inout(V) defaultValue) inout
1403         {
1404             version(LDC) pragma(inline, true);
1405             if (auto valuePtr = key in this)
1406                 return *valuePtr;
1407             else
1408                 return defaultValue;
1409         }
1410 
1411         /** Get reference to `key`-part of stored element at `key`, if present,
1412          * otherwise return `defaultKey`.
1413          *
1414          * Used to implement caching inside the key part of a map.
1415          */
1416         ref const(K) getKeyRef(SomeKey)(const scope SomeKey key, /* template-lazy */
1417                                         ref const(K) defaultKey) const return @trusted @nogc
1418         if (isScopedKeyType!(SomeKey))
1419         {
1420             version(LDC) pragma(inline, true);
1421             immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted
1422 			immutable match = hitIndex != _store.length && isOccupiedAtIndex(hitIndex);
1423             if (match)
1424                 return _store[hitIndex].key;
1425             return defaultKey;
1426         }
1427 
1428         /** Supports the syntax `aa[key] = value;`.
1429          */
1430         ref V opIndexAssign()(V value, K key) /* template-lazy. TODO: return scope */
1431         in(!key.isNull)
1432         {
1433             version(LDC) pragma(inline, true);
1434 
1435             static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(key)); }
1436             static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
1437 
1438             reserveExtra(1);
1439 
1440             static if (__traits(isPOD, K))
1441             {
1442                 static if (__traits(isPOD, V))
1443                     immutable hitIndex = insertWithoutGrowthNoStatus(T(key, value));
1444                 else
1445                     immutable hitIndex = insertWithoutGrowthNoStatus(T(key, move(value)));
1446             }
1447             else
1448             {
1449                 static if (__traits(isPOD, V))
1450                     immutable hitIndex = insertWithoutGrowthNoStatus(T(move(key), value));
1451                 else
1452                     immutable hitIndex = insertWithoutGrowthNoStatus(T(move(key), move(value)));
1453             }
1454 
1455             return _store[hitIndex].value;
1456         }
1457 
1458         ref V opIndexOpAssign(string op, Rhs)(Rhs rhs, K key) // TODO: return scope
1459         // if (true)               // TODO: pre-check that mixin will work
1460         in(!key.isNull)
1461         {
1462             // pragma(msg, "opIndexOpAssign: Key:", K, " Value:", V, " Rhs:", Rhs, " op:", op);
1463             static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(key)); }
1464             static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
1465 
1466             reserveExtra(1);
1467 
1468             size_t holeIndex = size_t.max; // first hole index to written to if hole found
1469             immutable hitIndex = indexOfKeyOrVacancyAndFirstHole(key, holeIndex);
1470             if (hitIndex == _store.length || // keys miss and holes may have filled all empty slots
1471                 keyOf(_store[hitIndex]).isNull) // just key miss but a hole may have been found on the way
1472             {
1473                 immutable hasHole = holeIndex != size_t.max; // hole was found along the way
1474                 immutable index = (hasHole ?
1475                                holeIndex : // pick hole instead
1476                                hitIndex); // normal hit
1477                 version(unittest) assert(index != _store.length, "no null or hole slot");
1478                 static if (__traits(isCopyable, K))
1479                 {
1480                     static if (op == "~" ||
1481                                op == "+" ||
1482                                op == "*")
1483                     {
1484                         static if (is(V : Rhs[])) // isDynamicArray of `Rhs`
1485                             insertElementAtIndex(T(key, [rhs]), // TODO: if `V(rhs)` is not supported use `V.init` followed by `OP= rhs`
1486                                                  index);
1487                         else
1488                             // dbg("opIndexOpAssign-new: k:", key, " rhs:", rhs);
1489                             insertElementAtIndex(T(key, V(rhs)), // TODO: if `V(rhs)` is not supported use `V.init` followed by `OP= rhs`
1490                                                  index);
1491                     }
1492                     else
1493                         static assert(0, "Handel op " ~ op);
1494                 }
1495                 else
1496                     static assert(0, "Handle uncopyable key " ~ K.stringof);
1497                     // insertElementAtIndex(move(*cast(SomeElement*)&element), index);
1498 
1499                 static if (!hasHoleableKey)
1500                     if (hasHole) { untagHoleAtIndex(index); }
1501 
1502                 _count = _count + 1;
1503                 return _store[index].value;
1504             }
1505             else                // `key`-hit at index `hitIndex`
1506             {
1507                 // dbg("opIndexOpAssign-mod: k:", key, " rhs:", rhs);
1508                 mixin(`return _store[hitIndex].value ` ~ op ~ `= rhs;`); // modify existing value
1509             }
1510         }
1511     }
1512 
1513     /** Remove `element`.
1514      * Returns: `true` if element was removed, `false` otherwise.
1515      */
1516     bool remove(SomeKey)(const scope SomeKey key) scope /* template-lazy */
1517     if (isScopedKeyType!(typeof(key)))
1518     {
1519         version(LDC) pragma(inline, true);
1520         static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
1521 
1522         static if (linearSearchMaxSize != 0)
1523             if (_store.length * T.sizeof <= linearSearchMaxSize)
1524             {
1525                 foreach (immutable i, const ref element; _store) // linear search is faster for small arrays
1526                     if (keyEqualPredFn(keyOf(element), key))
1527                     {
1528                         tagAsLazilyDeletedElementAtIndex(i);
1529                         _count = _count - 1;
1530                         return true;
1531                     }
1532                 return false;
1533             }
1534 
1535         immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKey(key));
1536 		immutable match = hitIndex != _store.length && isOccupiedAtIndex(hitIndex);
1537         if (match)
1538         {
1539             tagAsLazilyDeletedElementAtIndex(hitIndex);
1540             _count = _count - 1;
1541             return true;
1542         }
1543         return false;
1544     }
1545 
1546     /** Remove all elements matching `keys` followed by a rehash.
1547      *
1548      * Returns: number of elements that were removed.
1549      */
1550     version(none)
1551     {
1552         import nxt.traits_ex : isRefIterable;
1553         import std.range.primitives : front;
1554 
1555         size_t rehashingRemoveN(Keys)(const scope Keys keys) /* template-lazy */
1556         if (isRefIterable!Keys &&
1557             is(typeof(Keys.front == K.init)))
1558         {
1559             static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
1560             rehash!("!a.isNull && keys.canFind(a)")(); // TODO: make this work
1561             return 0;
1562         }
1563     }
1564 
1565     /// Check if empty.
1566     bool empty() const @property => _count == 0;
1567 
1568     /// Get length (read-only).
1569     @property size_t length() const => _count;
1570 
1571     /// Get bin count.
1572     @property size_t binCount() const => _store.length;
1573 
1574     /** Returns: get total probe count for all elements stored. */
1575     size_t totalProbeCount()() const /* template-lazy */
1576     {
1577         static if (hasValue)
1578             auto range = byKeyValue(this);
1579         else
1580             auto range = byElement(this);
1581 
1582         typeof(return) totalCount = 0;
1583 
1584         foreach (const ref currentElement; range)
1585         {
1586             static if (__traits(isCopyable, T)) // TODO why does using `passElementByValue` fail as an expression for certain element types?
1587                 /* don't use `auto ref` for copyable `T`'s to prevent
1588                  * massive performance drop for small elements when compiled
1589                  * with LDC. TODO: remove when LDC is fixed. */
1590                 alias pred = (const scope element) => (keyEqualPredFn(keyOf(element),
1591                                                                       keyOf(currentElement)));
1592             else
1593                 alias pred = (const scope ref element) => (keyEqualPredFn(keyOf(element),
1594                                                                           keyOf(currentElement)));
1595 
1596             static if (usePrimeCapacity)
1597                 immutable probeCount = xxx;
1598             else
1599                 immutable probeCount = triangularProbeCountFromIndex!(pred)(_store[], keyToIndex(keyOf(currentElement)));
1600 
1601             totalCount += probeCount;
1602         }
1603         return totalCount;
1604     }
1605 
1606     /** Returns: average probe count for all elements stored. */
1607     double averageProbeCount()() const /* template-lazy */
1608 		=> (cast(typeof(return))totalProbeCount)/length;
1609 
1610     /** Unsafe access to raw store.
1611      *
1612      * Needed by wrapper containers such as `SSOHybridHashSet`.
1613      */
1614 	pragma(inline, true)
1615     inout(T)[] rawStore() inout @system pure nothrow @nogc => _store;
1616 
1617     static if (hasHoleableKey)
1618     {
1619         static bool isOccupiedBin(const ref T bin)
1620 			=> (keyOf(bin).isNull &&
1621 				!isHoleKeyConstant(keyOf(bin)));
1622     }
1623 
1624 private:
1625     import nxt.allocator_traits : AllocatorState;
1626     mixin AllocatorState!Allocator; // put first as emsi-containers do
1627 
1628     static if (hasFunctionAttributes!(allocator.allocate, "@nogc"))
1629     {
1630         import nxt.gc_traits : NoGc;
1631         @NoGc T[] _store;        // one element per bin
1632     }
1633     else
1634         T[] _store;              // one element per bin
1635 
1636     static if (usePrimeCapacity)
1637         PrimeIndex _primeIndex = PrimeIndex.init;
1638 
1639     static if (!hasHoleableKey)
1640     {
1641         static if (hasFunctionAttributes!(allocator.allocate, "@nogc"))
1642             @NoGc size_t* _holesPtr; // bit array describing which bin elements that has been removed (holes)
1643         else
1644             size_t* _holesPtr; // bit array describing which bin elements that has been removed (holes)
1645     }
1646 
1647     static if (isBorrowChecked)
1648     {
1649         debug // use Rust-style borrow checking at run-time
1650         {
1651             size_t _count;
1652             size_t _borrowCount;
1653 
1654             /// Number of bits needed to store number of read borrows.
1655             enum borrowCountBits = 8*isBorrowChecked.sizeof;
1656 
1657             /// Maximum value possible for `_borrowCount`.
1658             enum borrowCountMax = 2^^borrowCountBits - 1;
1659 
1660             version(none)
1661             {
1662                 /// Number of bits needed to store number of read borrows.
1663                 enum borrowCountBits = 24;
1664 
1665                 /// Maximum value possible for `_borrowCount`.
1666                 enum borrowCountMax = 2^^borrowCountBits - 1;
1667 
1668                 import std.bitmanip : bitfields;
1669                 mixin(bitfields!(size_t, "_count", 8*size_t.sizeof - borrowCountBits,
1670                                  uint, "_borrowCount", borrowCountBits,
1671                           ));
1672             }
1673 
1674             pragma(inline, true):
1675             @safe pure nothrow @nogc:
1676 
1677             @property
1678             {
1679                 /// Returns: `true` iff `this` is borrowed (either read or write).
1680                 bool isBorrowed() const => _borrowCount >= 1;
1681 
1682                 /// Returns: number of borrowers of `this` (both read and write).
1683                 auto borrowCount() const => _borrowCount;
1684             }
1685 
1686             /// Increase borrow count.
1687             void incBorrowCount()
1688 			in(_borrowCount + 1 != borrowCountMax)
1689             {
1690                 _borrowCount = _borrowCount + 1;
1691             }
1692 
1693             /// Decrease borrow count.
1694             void decBorrowCount()
1695 			in(_borrowCount != 0)
1696             {
1697                 _borrowCount = _borrowCount - 1;
1698             }
1699         }
1700         else
1701         {
1702             size_t _count;        // total number of non-null elements stored in `_store`
1703         }
1704     }
1705     else
1706     {
1707         size_t _count;        // total number of non-null elements stored in `_store`
1708     }
1709 
1710     /** Returns: bin index of `key`. */
1711     private size_t keyToIndex(SomeKey)(const scope SomeKey key) const @trusted
1712     {
1713         version(LDC) pragma(inline, true); // TODO: inline always
1714 
1715         /** Returns: current index mask from bin count `_store.length`.
1716 		 * TODO: Inline this and check for speed-up.
1717 		 */
1718         static size_t powerOf2Mask(in size_t length) @safe pure nothrow @nogc
1719         {
1720             version(unittest)
1721             {
1722 				// TODO: move to in contract:
1723                 debug import std.math : isPowerOf2;
1724                 debug assert(isPowerOf2(length));
1725             }
1726 			else
1727 			{
1728 				version(D_Coverage) {} else pragma(inline, true);
1729 			}
1730             return length - 1;
1731         }
1732 
1733         static if (is(typeof(hasher(key)) == hash_t)) // for instance when hasher being `hashOf`
1734             immutable hash = hasher(key);
1735         else static if (is(hasher == struct) || // such as `FNV`
1736                         is(hasher == class))
1737         {
1738             import nxt.digestion : hashOf2;
1739             immutable hash = hashOf2!(hasher)(key);
1740         }
1741         else
1742             static assert(false, "Unsupported hasher of type " ~ typeof(hasher).stringof);
1743 
1744         static if (usePrimeCapacity)
1745             return moduloPrimeIndex(hash, _primeIndex);
1746         else
1747             return hash & powerOf2Mask(_store.length);
1748     }
1749 
1750     /** Find index to `key` if it exists or to first empty slot found, skipping
1751      * (ignoring) lazily deleted slots.
1752      */
1753     private size_t indexOfKeyOrVacancySkippingHoles(const scope K key) const @trusted scope // `auto ref` here makes things slow
1754     // TODO: if (...)
1755     {
1756         version(LDC) pragma(inline, true);
1757         version(unittest)
1758         {
1759             assert(!key.isNull);
1760             static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); }
1761         }
1762 
1763         static if (linearSearchMaxSize != 0)
1764             if (_store.length * T.sizeof <= linearSearchMaxSize)
1765             {
1766                 foreach (immutable i, const ref element; _store) // linear search is faster for small arrays
1767                     if ((keyOf(element).isNull ||
1768                          keyEqualPredFn(keyOf(element), key)))
1769                         return i;
1770                 return _store.length;
1771             }
1772 
1773         static if (passElementByValue) // Ref: https://github.com/dlang/dmd/pull/11000#issuecomment-671103778
1774         {
1775             static if (hasHoleableKey)
1776                 alias pred = (const scope element) => (keyOf(element).isNull ||
1777                                                        keyEqualPredFn(keyOf(element), key));
1778             else
1779                 alias pred = (immutable scope index,
1780                               const scope element) => (!hasHoleAtPtrIndex(_holesPtr, index) &&
1781                                                        (keyOf(element).isNull ||
1782                                                         keyEqualPredFn(keyOf(element), key)));
1783         }
1784         else
1785         {
1786             static if (hasHoleableKey)
1787                 alias pred = (const scope auto ref element) => (keyOf(element).isNull ||
1788                                                                 keyEqualPredFn(keyOf(element), key));
1789             else
1790                 alias pred = (immutable scope index,
1791                               const scope auto ref element) => (!hasHoleAtPtrIndex(_holesPtr, index) &&
1792                                                                 (keyOf(element).isNull ||
1793                                                                  keyEqualPredFn(keyOf(element), key)));
1794         }
1795 
1796         static if (usePrimeCapacity)
1797             return xxx;
1798         else
1799             return _store[].triangularProbeFromIndex!(pred, assumeNonFullHaystack)(keyToIndex(key));
1800     }
1801 
1802     private size_t indexOfKeyOrVacancyAndFirstHole(const scope K key, // `auto ref` here makes things slow
1803                                                    ref size_t holeIndex) const @trusted scope
1804     {
1805         version(LDC) pragma(inline, true);
1806         version(unittest)
1807         {
1808             assert(!key.isNull);
1809             static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); }
1810         }
1811 
1812         static if (linearSearchMaxSize != 0)
1813             if (_store.length * T.sizeof <= linearSearchMaxSize)
1814             {
1815                 foreach (immutable i, const ref element; _store) // linear search is faster for small arrays
1816                     if ((keyOf(element).isNull ||
1817                          keyEqualPredFn(keyOf(element), key)))
1818                         return i;
1819                 return _store.length;
1820             }
1821 
1822         static if (passElementByValue) // Ref: https://github.com/dlang/dmd/pull/11000#issuecomment-671103778
1823         {
1824             static if (hasHoleableKey)
1825             {
1826                 alias hitPred = (const scope element) => (keyOf(element).isNull ||
1827                                                           keyEqualPredFn(keyOf(element), key));
1828                 alias holePred = (const scope element) => (isHoleKeyConstant(keyOf(element)));
1829             }
1830             else
1831             {
1832                 alias hitPred = (immutable scope index,
1833                                  const scope element) => (!hasHoleAtPtrIndex(_holesPtr, index) &&
1834                                                           (keyOf(element).isNull ||
1835                                                            keyEqualPredFn(keyOf(element), key)));
1836                 alias holePred = (immutable scope index, // TODO: use only index
1837                                   const scope element) => (hasHoleAtPtrIndex(_holesPtr, index));
1838             }
1839         }
1840         else
1841         {
1842             static if (hasHoleableKey)
1843             {
1844                 alias hitPred = (const scope auto ref element) => (keyOf(element).isNull ||
1845                                                                    keyEqualPredFn(keyOf(element), key));
1846                 alias holePred = (const scope auto ref element) => (isHoleKeyConstant(keyOf(element)));
1847             }
1848             else
1849             {
1850                 alias hitPred = (immutable scope index,
1851                                  const scope auto ref element) => (!hasHoleAtPtrIndex(_holesPtr, index) &&
1852                                                                    (keyOf(element).isNull ||
1853                                                                     keyEqualPredFn(keyOf(element), key)));
1854                 alias holePred = (immutable scope index, // TODO: use only index
1855                                   const scope auto ref element) => (hasHoleAtPtrIndex(_holesPtr, index));
1856             }
1857         }
1858 
1859         static if (usePrimeCapacity)
1860             return xxx;
1861         else
1862             return _store[].triangularProbeFromIndexIncludingHoles!(hitPred, holePred, assumeNonFullHaystack)(keyToIndex(key), holeIndex);
1863     }
1864 
1865     /// Returns: `true` iff `index` indexes a non-null element, `false` otherwise.
1866     private bool isOccupiedAtIndex(in size_t index) const
1867     {
1868         version(LDC) pragma(inline, true);
1869         version(unittest) assert(index < _store.length);
1870         if (keyOf(_store[index]).isNull) { return false; }
1871         static if (hasHoleableKey)
1872             return !isHoleKeyConstant(keyOf(_store[index]));
1873         else
1874             return !hasHoleAtPtrIndex(_holesPtr, index);
1875     }
1876 }
1877 
1878 /** Duplicate `src` into uninitialized `dst` ignoring prior destruction of `dst`.
1879  *
1880  * TODO: Move to a more generic place either in phobos-next or Phobos.
1881  */
1882 static private void duplicateEmplace(T)(const scope ref T src,
1883                                         scope ref T dst) @system
1884 {
1885     version(D_Coverage) {} else pragma(inline, true);
1886     import core.internal.traits : hasElaborateCopyConstructor;
1887     import std.traits : isBasicType;
1888     static if (!hasElaborateCopyConstructor!T)
1889     {
1890         import std.typecons : Nullable;
1891         static if (is(T == class) ||
1892                    is(T == string))
1893             dst = cast(T)src;
1894         else static if (isBasicType!T || is(T == Nullable!(_), _)) // `Nullable` types cannot be emplaced
1895             dst = src;
1896         else                    // TODO: can this case occur?
1897         {
1898             import core.internal.traits : Unqual;
1899             import core.lifetime : emplace;
1900             emplace(&dst, cast(Unqual!T)src);
1901         }
1902     }
1903     else static if (__traits(hasMember, T, "dup"))
1904     {
1905         import core.lifetime : emplace;
1906         // TODO: when `emplace` can handle src being an r-value of uncopyable types replace with: `emplace(&dst, src.dup);`
1907         emplace(&dst);
1908         dst = src.dup;
1909     }
1910     else
1911         debug static assert(0, "cannot duplicate a " ~ T.stringof);
1912 }
1913 
1914 /** L-value element reference (and in turn range iterator).
1915  */
1916 static private struct LvalueElementRef(SomeMap)
1917 {
1918     import std.traits : isMutable;
1919     debug static assert(isMutable!SomeMap, "SomeMap type must be mutable");
1920 
1921     private SomeMap* _table;      // scoped access
1922     private size_t _binIndex;   // index to bin inside `table`
1923     private size_t _hitCounter; // counter over number of elements popped (needed for length)
1924 
1925     this(SomeMap* table) @trusted
1926     {
1927         version(D_Coverage) {} else pragma(inline, true);
1928         this._table = table;
1929         static if (SomeMap.isBorrowChecked)
1930             debug { _table.incBorrowCount(); }
1931     }
1932 
1933     ~this() nothrow @nogc @trusted
1934     {
1935         version(D_Coverage) {} else pragma(inline, true);
1936         static if (SomeMap.isBorrowChecked)
1937             debug { _table.decBorrowCount(); }
1938     }
1939 
1940     this(this) @trusted
1941     {
1942         version(D_Coverage) {} else pragma(inline, true);
1943         static if (SomeMap.isBorrowChecked)
1944             debug
1945             {
1946                 assert(_table._borrowCount != 0);
1947                 _table.incBorrowCount();
1948             }
1949     }
1950 
1951     /// Check if empty.
1952     bool empty() const @property @safe pure nothrow @nogc
1953     {
1954         version(D_Coverage) {} else pragma(inline, true);
1955         return _binIndex == _table.binCount;
1956     }
1957 
1958     /// Get number of element left to pop.
1959     @property size_t length() const @safe pure nothrow @nogc
1960     {
1961         version(D_Coverage) {} else pragma(inline, true);
1962         return _table.length - _hitCounter;
1963     }
1964 
1965     @property typeof(this) save() // ForwardRange
1966     {
1967         version(D_Coverage) {} else pragma(inline, true);
1968         return this;
1969     }
1970 
1971     void popFront() in(!empty)
1972     {
1973         version(LDC) pragma(inline, true);
1974         _binIndex += 1;
1975         findNextNonEmptyBin();
1976         _hitCounter += 1;
1977     }
1978 
1979     private void findNextNonEmptyBin()
1980     {
1981         version(D_Coverage) {} else pragma(inline, true);
1982         while (_binIndex != (*_table).binCount &&
1983                !(*_table).isOccupiedAtIndex(_binIndex))
1984             _binIndex += 1;
1985     }
1986 }
1987 
1988 /** R-value element reference (and in turn range iterator).
1989  *
1990  * Does need to do borrow-checking.
1991  */
1992 static private struct RvalueElementRef(SomeMap)
1993 {
1994     debug import std.traits : isMutable;
1995     debug static assert(isMutable!SomeMap, "SomeMap type must be mutable");
1996 
1997     SomeMap _table;                // owned table
1998     size_t _binIndex;            // index to bin inside table
1999     size_t _hitCounter;    // counter over number of elements popped
2000 
2001     /// Check if empty.
2002     bool empty() const @property @safe pure nothrow @nogc
2003     {
2004         version(D_Coverage) {} else pragma(inline, true);
2005         return _binIndex == _table.binCount;
2006     }
2007 
2008     /// Get number of element left to pop.
2009     @property size_t length() const @safe pure nothrow @nogc
2010     {
2011         version(D_Coverage) {} else pragma(inline, true);
2012         return _table.length - _hitCounter;
2013     }
2014 
2015     void popFront() in(!empty)
2016     {
2017         version(LDC) pragma(inline, true);
2018         _binIndex += 1;
2019         findNextNonEmptyBin();
2020         _hitCounter += 1;
2021     }
2022 
2023     private void findNextNonEmptyBin()
2024     {
2025         version(D_Coverage) {} else pragma(inline, true);
2026         while (_binIndex != _table.binCount &&
2027                !_table.isOccupiedAtIndex(_binIndex))
2028             _binIndex += 1;
2029     }
2030 }
2031 
2032 /** Hash set with in-place open-addressing, storing keys (elements) of type `K`.
2033  *
2034  * Reuse `HybridHashMap` with its V-type set to `void`.
2035  *
2036  * See_Also: `HybridHashMap`.
2037  */
2038 alias HybridHashSet(K,
2039 					alias hasher = hashOf,
2040 					string keyEqualPred = defaultKeyEqualPredOf!K,
2041 					Allocator = Mallocator,
2042 					BorrowCheckFlag borrowCheckFlag = BorrowCheckFlag.no,
2043 					uint linearSearchMaxSize = 64, // one cache-line for now
2044 					PrimeCapacityFlag primeCapacityFlag = PrimeCapacityFlag.no) =
2045 	HybridHashMap!(K, void, hasher, keyEqualPred, Allocator, borrowCheckFlag,
2046 				   linearSearchMaxSize, primeCapacityFlag);
2047 
2048 import std.functional : unaryFun;
2049 
2050 /** Remove all elements in `x` matching `pred`.
2051  *
2052  * TODO: make this generic for all iterable containers and move to
2053  * container/common.d.
2054  */
2055 size_t removeAllMatching(alias pred, SomeMap)(auto ref SomeMap x) @trusted
2056 if (is(SomeMap == HybridHashMap!(_), _...) && // TODO: generalize to `isSetOrMap`
2057     is(typeof((unaryFun!pred))))
2058 {
2059     import nxt.nullable_traits : nullify;
2060     size_t removalCount = 0;
2061     foreach (immutable i, ref bin; x._store)
2062     {
2063         // TODO:
2064         // move to SomeMap.removeRef(bin) // uses: `offset = &bin - _store.ptr`
2065         // move to SomeMap.inplaceRemove(bin) // uses: `offset = &bin - _store.ptr`
2066         // or   to SomeMap.removeAtIndex(i)
2067         if (x.isOccupiedAtIndex(i) &&
2068             unaryFun!pred(bin))
2069         {
2070             x.tagAsLazilyDeletedElementAtIndex(i);
2071             removalCount += 1;
2072         }
2073     }
2074     x._count = x._count - removalCount;
2075     return removalCount;        // TODO: remove this return value
2076 }
2077 
2078 /** Returns: `x` eagerly filtered on `pred`.
2079  *
2080  * TODO: move to container/common.d with more generic template restrictions
2081  */
2082 SomeMap filtered(alias pred, SomeMap)(SomeMap x)
2083 if (is(SomeMap == HybridHashMap!(_), _...)) // TODO: generalize to `isSetOrMap`
2084 {
2085     import core.lifetime : move;
2086     import std.functional : not;
2087     x.removeAllMatching!(not!pred); // `x` is a singleton (r-value) so safe to mutate
2088     return move(x);             // functional
2089 }
2090 
2091 /** Returns: `x` eagerly intersected with `y`.
2092  *
2093  * TODO: move to container/common.d.
2094  * TODO: Check that `ElementType`'s of `C1` and `C2` match. Look at std.algorithm.intersection for hints.
2095  */
2096 auto intersectedWith(C1, C2)(C1 x, auto ref C2 y)
2097 if (is(C1 == HybridHashMap!(_1), _1...) && // TODO: generalize to `isSetOrMap`
2098     is(C2 == HybridHashMap!(_2), _2...))   // TODO: generalize to `isSetOrMap`
2099 {
2100     import core.lifetime : move;
2101     static if (__traits(isRef, y)) // y is l-value
2102         // @("complexity", "O(x.length)")
2103         return move(x).filtered!(_ => y.contains(_)); // only x can be reused
2104     else
2105     {
2106         /* both are r-values so reuse the shortest */
2107         // @("complexity", "O(min(x.length), min(y.length))")
2108         if (x.length < y.length)
2109             return move(x).filtered!(_ => y.contains(_)); // functional
2110         else
2111             return move(y).filtered!(_ => x.contains(_)); // functional
2112     }
2113 }
2114 
2115 /// exercise opEquals
2116 @safe pure nothrow @nogc unittest
2117 {
2118     import std.typecons : Nullable;
2119     import nxt.digestx.fnv : FNV;
2120 
2121     alias K = Nullable!(ulong, ulong.max);
2122     alias X = HybridHashSet!(K, FNV!(64, true));
2123 
2124     const n = 100;
2125 
2126     X a;
2127     foreach (const i_; 0 .. n)
2128     {
2129         const i = 1113*i_;           // insert in order
2130         assert(!a.contains(K(i)));
2131         assert(!a.containsUsingLinearSearch(K(i)));
2132         assert(a.insertAndReturnElement(K(i)) == K(i));
2133         assert(a.contains(K(i)));
2134         assert(a.containsUsingLinearSearch(K(i)));
2135     }
2136 
2137     X b;
2138     foreach (const i_; 0 .. n)
2139     {
2140         const i = 1113*(n - 1 - i_);   // insert in reverse
2141         assert(!b.contains(K(i)));
2142         assert(!b.containsUsingLinearSearch(K(i)));
2143         assert(b.insertAndReturnElement(K(i)) == K(i));
2144         assert(b.contains(K(i)));
2145         assert(b.containsUsingLinearSearch(K(i)));
2146     }
2147 
2148     assert(a == b);
2149 
2150     // bin storage must be deterministic
2151     () @trusted { assert(a._store != b._store); }();
2152 }
2153 
2154 @safe pure nothrow @nogc unittest
2155 {
2156     import nxt.digestx.fnv : FNV;
2157 
2158     enum Pot { noun, verb }
2159     struct ExprPot
2160     {
2161         string expr;
2162         Pot pot;
2163 
2164         alias nullifier = expr;
2165         static immutable nullValue = typeof(this).init;
2166     }
2167 
2168     alias X = HybridHashSet!(ExprPot, FNV!(64, true));
2169 
2170     X x;
2171 
2172     const aa = "aa";
2173 
2174     // two keys with same contents but different place in memory
2175     const key1 = ExprPot(aa[0 .. 1], Pot.noun);
2176     const key2 = ExprPot(aa[1 .. 2], Pot.noun);
2177 
2178     assert(key1 == key2);
2179     assert(key1 !is key2);
2180 
2181     assert(!x.contains(key1));
2182     assert(!x.contains(key2));
2183     x.insert(key1);
2184     assert(x.contains(key1));
2185     assert(x.containsUsingLinearSearch(key1));
2186     assert(x.contains(key2));
2187     /* assert(x.containsUsingLinearSearch(key2)); */
2188     assert(key1 in x);
2189     assert(key2 in x);
2190 }
2191 
2192 /// `string` as key
2193 @safe pure nothrow @nogc unittest
2194 {
2195     import nxt.container.traits : mustAddGCRange;
2196     import nxt.digestx.fnv : FNV;
2197 
2198     alias X = HybridHashSet!(string, FNV!(64, true));
2199     debug static assert(!mustAddGCRange!X);
2200     debug static assert(X.sizeof == 24); // dynamic arrays also `hasAddressLikeKey`
2201 
2202     auto x = X();
2203 
2204     auto testEscapeShouldFail()() @safe pure
2205     {
2206         X x;
2207         x.insert("a");
2208         return x.byElement;
2209     }
2210 
2211     auto testEscapeShouldFailFront()() @safe pure
2212     {
2213         X x;
2214         x.insert("a");
2215         return x.byElement.front;
2216     }
2217 
2218     assert(&"a"[0] is &"a"[0]); // string literals are store in common place
2219 
2220     const aa = "aa";
2221 
2222     // string slices are equal when elements are equal regardless of position
2223     // (.ptr) in memory
2224     assert(x.insertAndReturnElement(aa[0 .. 1]) !is "a");
2225     x.insert(aa[0 .. 1]);
2226     assert(x.insertAndReturnElement(aa[0 .. 1]) is aa[0 .. 1]);
2227     assert(x.contains(aa[1 .. 2]));
2228     assert(x.containsUsingLinearSearch(aa[1 .. 2]));
2229 
2230     const(char)[] aa_ = "aa";
2231     assert(x.contains(aa_[1 .. 2]));
2232     assert(x.containsUsingLinearSearch(aa_[1 .. 2]));
2233     assert(aa_[1 .. 2] in x);
2234 
2235     char[2] aa__; aa__ = "aa";
2236     assert(x.contains(aa__[1 .. 2]));
2237     assert(x.containsUsingLinearSearch(aa__[1 .. 2]));
2238     assert(aa__[1 .. 2] in x);
2239 
2240     const bb = "bb";
2241 
2242     assert(x.insertAndReturnElement(bb[0 .. 1]) is bb[0 .. 1]); // returns newly added ref
2243     assert(x.insertAndReturnElement(bb[0 .. 1]) !is "b");       // return other ref not equal new literal
2244     x.insert(bb[0 .. 1]);
2245     assert(x.contains(bb[1 .. 2]));
2246     assert(x.containsUsingLinearSearch(bb[1 .. 2]));
2247 
2248     x.remove(aa[0 .. 1]);
2249     assert(!x.contains(aa[1 .. 2]));
2250     assert(!x.containsUsingLinearSearch(aa[1 .. 2]));
2251     assert(x.contains(bb[1 .. 2]));
2252     assert(x.containsUsingLinearSearch(bb[1 .. 2]));
2253 
2254     x.remove(bb[0 .. 1]);
2255     assert(!x.contains(bb[1 .. 2]));
2256     assert(!x.containsUsingLinearSearch(bb[1 .. 2]));
2257 
2258     x.insert("a");
2259     x.insert("b");
2260     assert(x.contains("a"));
2261     assert(x.containsUsingLinearSearch("a"));
2262     assert(x.contains("b"));
2263     assert(x.containsUsingLinearSearch("b"));
2264 
2265     debug static assert(!__traits(compiles, { testEscapeShouldFail(); } ));
2266     // TODO: this should fail:
2267     // TODO: debug static assert(!__traits(compiles, { testEscapeShouldFailFront(); } ));
2268 }
2269 
2270 /// `string` as key
2271 @safe pure nothrow unittest
2272 {
2273     import nxt.digestx.fnv : FNV;
2274     alias X = HybridHashSet!(string, FNV!(64, true));
2275     auto x = X();
2276 
2277     char[2] cc = "cc";          // mutable chars
2278     assert(x.insertAndReturnElement(cc[]) !is cc[]); // will allocate new slice
2279 
2280     const cc_ = "cc";           // immutable chars
2281     assert(x.insertAndReturnElement(cc_[]) !is cc[]); // will not allocate
2282 }
2283 
2284 /// array container as value type
2285 @safe pure nothrow @nogc unittest
2286 {
2287     import std.meta : AliasSeq;
2288     import std.typecons : Nullable;
2289     import nxt.container.traits : mustAddGCRange;
2290     import nxt.digestx.fnv : FNV;
2291     import nxt.array_help : s;
2292 
2293     alias K = Nullable!(uint, uint.max);
2294 
2295     alias VE = Nullable!(uint, uint.max);
2296     alias V = HybridHashSet!(VE, FNV!(64, true));
2297 
2298     debug static assert(!mustAddGCRange!V);
2299 
2300     foreach (X; AliasSeq!(HybridHashMap!(K, V, FNV!(64, true))))
2301     {
2302         const VE n = 600;
2303 
2304         auto x = X();
2305 
2306         {                       // scoped range
2307             auto xkeys = x.byKey;
2308             assert(xkeys.length == 0);
2309             foreach (ref key; xkeys)
2310             {
2311                 debug static assert(is(typeof(key) == const(K)));
2312                 assert(0);
2313             }
2314             foreach (ref key; X().byKey)
2315             {
2316                 debug static assert(is(typeof(key) == const(K)));
2317                 assert(0);
2318             }
2319         }
2320 
2321         foreach (immutable i; 0 .. n)
2322         {
2323             assert(x.length == i);
2324 
2325             auto key = K(i);
2326             auto value = V.withElements([VE(i)].s);
2327 
2328             x[key] = value.dup;
2329             assert(x.length == i + 1);
2330             assert(x.contains(key));
2331             // TODO: assert(x.containsUsingLinearSearch(key));
2332             {
2333                 auto valuePtr = key in x;
2334                 assert(valuePtr && *valuePtr == value);
2335             }
2336 
2337             x.remove(key);
2338             assert(x.length == i);
2339             assert(!x.contains(key));
2340             assert(key !in x);
2341 
2342             x[key] = value.dup;
2343             assert(x.length == i + 1);
2344             assert(x.contains(key));
2345             {
2346                 auto valuePtr = key in x;
2347                 assert(valuePtr && *valuePtr == value);
2348             }
2349         }
2350 
2351         assert(x is x);
2352 
2353         x = x.dup;
2354 
2355         auto y = x.dup;
2356         assert(x !is y);
2357         assert(x.length == y.length);
2358 
2359         assert(y == x);
2360         assert(x == y);
2361 
2362         foreach (ref key; x.byKey)
2363         {
2364             assert(x.contains(key));
2365         }
2366 
2367         foreach (ref keyValue; x.byKeyValue)
2368         {
2369             assert(x.contains(keyValue.key));
2370             auto keyValuePtr = keyValue.key in x;
2371             assert(keyValuePtr &&
2372                    *keyValuePtr == keyValue.value);
2373         }
2374 
2375         foreach (immutable i; 0 .. n)
2376         {
2377             assert(x.length == n - i);
2378 
2379             auto key = K(i);
2380             auto value = V.withElements([VE(i)].s);
2381 
2382             assert(x.contains(key));
2383             {
2384                 auto valuePtr = key in x;
2385                 assert(valuePtr && *valuePtr == value);
2386             }
2387 
2388             x.remove(key);
2389             assert(!x.contains(key));
2390             assert(key !in x);
2391         }
2392 
2393         auto z = y.dup;
2394         assert(y == z);
2395 
2396         /* remove all elements in `y` using `removeAllMatching` and all elements
2397          * in `z` using `removeAllMatching` */
2398         foreach (immutable i; 0 .. n)
2399         {
2400             assert(y.length == n - i);
2401             assert(z.length == n - i);
2402 
2403             auto key = K(i);
2404             auto value = V.withElements([VE(i)].s);
2405 
2406             assert(y.contains(key));
2407             {
2408                 auto valuePtr = key in y;
2409                 assert(valuePtr && *valuePtr == value);
2410             }
2411             assert(z.contains(key));
2412             {
2413                 auto valuePtr = key in z;
2414                 assert(valuePtr && *valuePtr == value);
2415             }
2416 
2417             y.remove(key);
2418             assert(z.removeAllMatching!((const scope ref element) => element.key is key) == 1);
2419             assert(y == z);
2420 
2421             assert(!y.contains(key));
2422             assert(!z.contains(key));
2423 
2424             assert(key !in y);
2425             assert(key !in z);
2426         }
2427     }
2428 }
2429 
2430 /// r-value and l-value intersection
2431 @safe pure nothrow @nogc unittest
2432 {
2433     import core.lifetime : move;
2434     import std.typecons : Nullable;
2435     import nxt.digestx.fnv : FNV;
2436     import nxt.array_help : s;
2437 
2438     alias K = Nullable!(uint, uint.max);
2439     alias X = HybridHashSet!(K, FNV!(64, true));
2440 
2441     auto x = X();
2442 
2443     {                           // scoped range
2444         foreach (ref _; x.byElement) { assert(0); }
2445     }
2446 
2447     auto x0 = X.init;
2448     assert(x0.length == 0);
2449     assert(x0._store.length == 0);
2450     assert(!x0.contains(K(1)));
2451 
2452     auto x1 = X.withElements([K(12)].s);
2453     assert(x1.length == 1);
2454     assert(x1.contains(K(12)));
2455 
2456     auto x2 = X.withElements([K(10), K(12)].s);
2457     assert(x2.length == 2);
2458     assert(x2.contains(K(10)));
2459     assert(x2.contains(K(12)));
2460 
2461     auto x3 = X.withElements([K(12), K(13), K(14)].s);
2462     assert(x3.length == 3);
2463     assert(x3.contains(K(12)));
2464     assert(x3.contains(K(13)));
2465     assert(x3.contains(K(14)));
2466 
2467     auto z = X.withElements([K(10), K(12), K(13), K(15)].s);
2468     assert(z.length == 4);
2469     assert(z.contains(K(10)));
2470     assert(z.contains(K(12)));
2471     assert(z.contains(K(13)));
2472     assert(z.contains(K(15)));
2473 
2474     auto y = move(z).intersectedWith(x2);
2475     assert(y.length == 2);
2476     assert(y.contains(K(10)));
2477     assert(y.contains(K(12)));
2478     assert(y.containsUsingLinearSearch(K(10)));
2479     assert(y.containsUsingLinearSearch(K(12)));
2480 }
2481 
2482 /// r-value and r-value intersection
2483 @safe pure nothrow @nogc unittest
2484 {
2485     import std.typecons : Nullable;
2486     import nxt.digestx.fnv : FNV;
2487     import nxt.array_help : s;
2488 
2489     alias K = Nullable!(uint, uint.max);
2490     alias X = HybridHashSet!(K, FNV!(64, true));
2491 
2492     auto y = X.withElements([K(10), K(12), K(13), K(15)].s).intersectedWith(X.withElements([K(12), K(13)].s));
2493     assert(y.length == 2);
2494     assert(y.contains(K(12)));
2495     assert(y.contains(K(13)));
2496     assert(y.containsUsingLinearSearch(K(12)));
2497     assert(y.containsUsingLinearSearch(K(13)));
2498 }
2499 
2500 /** Returns: `x` eagerly intersected with `y`.
2501     TODO: move to container/common.d.
2502  */
2503 auto intersectWith(C1, C2)(ref C1 x, auto ref const(C2) y)
2504 if (is(C1 == HybridHashMap!(_1), _1) &&
2505     is(C2 == HybridHashMap!(_2), _2))
2506 {
2507     return x.removeAllMatching!(_ => !y.contains(_));
2508 }
2509 
2510 /// r-value and l-value intersection
2511 @safe pure nothrow @nogc unittest
2512 {
2513     import std.typecons : Nullable;
2514     import nxt.digestx.fnv : FNV;
2515     import nxt.array_help : s;
2516 
2517     alias K = Nullable!(uint, uint.max);
2518     alias X = HybridHashSet!(K, FNV!(64, true));
2519 
2520     auto x = X.withElements([K(12), K(13)].s);
2521     auto y = X.withElements([K(10), K(12), K(13), K(15)].s);
2522     y.intersectWith(x);
2523     assert(y.length == 2);
2524     assert(y.contains(K(12)));
2525     assert(y.containsUsingLinearSearch(K(12)));
2526     assert(y.contains(K(13)));
2527     assert(y.containsUsingLinearSearch(K(13)));
2528 }
2529 
2530 /// Range over elements of l-value instance of this.
2531 static struct ByLvalueElement(SomeMap) // public for now because this is needed in `knet.zing.Zing.EdgesOfRels`
2532 {
2533 pragma(inline, true):
2534     // TODO: functionize
2535     static if (isAddress!(SomeMap.ElementType)) // for reference types
2536     {
2537         /// Get reference to front element.
2538         @property scope SomeMap.ElementType front()() return @trusted
2539         {
2540             // cast to head-const for class key
2541             return (cast(typeof(return))_table._store[_binIndex]);
2542         }
2543     }
2544     else
2545     {
2546         /// Get reference to front element.
2547         @property scope auto ref front() return @trusted
2548         {
2549             return *(cast(const(SomeMap.ElementType)*)&_table._store[_binIndex]); // propagate constnes
2550         }
2551     }
2552     import core.internal.traits : Unqual;
2553     // unqual to reduce number of instantations of `LvalueElementRef`
2554     public LvalueElementRef!(Unqual!SomeMap) _elementRef;
2555     alias _elementRef this;
2556 }
2557 
2558 /// Range over elements of r-value instance of this.
2559 static private struct ByRvalueElement(SomeMap)
2560 {
2561     @disable this(this);
2562 pragma(inline, true):
2563     static if (isAddress!(SomeMap.ElementType)) // for reference types
2564     {
2565         /// Get reference to front element.
2566         @property scope SomeMap.ElementType front()() return @trusted
2567         {
2568             // cast to head-const for class key
2569             return cast(typeof(return))_table._store[_binIndex];
2570         }
2571     }
2572     else
2573     {
2574         /// Get reference to front element.
2575         @property scope auto ref front() return
2576         {
2577             return *(cast(const(SomeMap.ElementType)*)&_table._store[_binIndex]); // propagate constnes
2578         }
2579     }
2580     import core.internal.traits : Unqual;
2581     public RvalueElementRef!(Unqual!SomeMap) _elementRef;
2582     alias _elementRef this;
2583 }
2584 
2585 /** Returns: range that iterates through the elements of `c` in undefined order.
2586  */
2587 auto byElement(SomeMap)(auto ref return SomeMap c) @trusted
2588 if (is(SomeMap == HybridHashMap!(_), _...) &&
2589     !SomeMap.hasValue)
2590 {
2591     import core.internal.traits : Unqual;
2592     alias M = Unqual!SomeMap;
2593     alias C = const(SomeMap);        // be const for now
2594     static if (__traits(isRef, c)) // `c` is an l-value
2595         auto result = ByLvalueElement!C((LvalueElementRef!(M)(cast(M*)&c)));
2596     else                        // `c` was is an r-value and can be moved
2597     {
2598         import core.lifetime : move;
2599         auto result = ByRvalueElement!C((RvalueElementRef!(M)(move(*(cast(M*)&c))))); // reinterpret
2600     }
2601     result.findNextNonEmptyBin();
2602     return result;
2603 }
2604 alias range = byElement;        // EMSI-container naming
2605 
2606 static private struct ByKey_lvalue(SomeMap)
2607 if (is(SomeMap == HybridHashMap!(_), _...) &&
2608     SomeMap.hasValue)
2609 {
2610     @property const scope auto ref front() return // key access must be const, TODO: auto ref => ref K
2611     {
2612         version(D_Coverage) {} else pragma(inline, true);
2613         return _table._store[_binIndex].key;
2614     }
2615     import core.internal.traits : Unqual;
2616     public LvalueElementRef!(Unqual!SomeMap) _elementRef;
2617     alias _elementRef this;
2618 }
2619 
2620 static private struct ByKey_rvalue(SomeMap)
2621 if (is(SomeMap == HybridHashMap!(_), _...) &&
2622     SomeMap.hasValue)
2623 {
2624     @property const scope auto ref front() return // key access must be const, TODO: auto ref => ref K
2625     {
2626         version(D_Coverage) {} else pragma(inline, true);
2627         return _table._store[_binIndex].key;
2628     }
2629     import core.internal.traits : Unqual;
2630     public RvalueElementRef!(Unqual!SomeMap) _elementRef;
2631     alias _elementRef this;
2632 }
2633 
2634 /** Returns: range that iterates through the keys of `c` in undefined order.
2635  */
2636 auto byKey(SomeMap)(auto ref /*TODO: return*/ SomeMap c) @trusted
2637 if (is(SomeMap == HybridHashMap!(_), _...) &&
2638     SomeMap.hasValue)
2639 {
2640     import core.internal.traits : Unqual;
2641     alias M = Unqual!SomeMap;
2642     alias C = const(SomeMap);        // be const
2643     static if (__traits(isRef, c)) // `c` is an l-value
2644         auto result = ByKey_lvalue!C((LvalueElementRef!(M)(cast(M*)&c)));
2645     else                        // `c` was is an r-value and can be moved
2646     {
2647         import core.lifetime : move;
2648         auto result = ByKey_rvalue!C((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret
2649     }
2650     result.findNextNonEmptyBin();
2651     return result;
2652 }
2653 
2654 static private struct ByValue_lvalue(SomeMap)
2655 if (is(SomeMap == HybridHashMap!(_), _...) &&
2656     SomeMap.hasValue)
2657 {
2658     @property scope auto ref front() return @trusted // TODO: auto ref => ref V
2659     {
2660         version(D_Coverage) {} else pragma(inline, true);
2661         // TODO: functionize
2662         import std.traits : isMutable;
2663         static if (isMutable!(SomeMap)) // TODO: can this be solved without this `static if`?
2664             alias E = SomeMap.ValueType;
2665         else
2666             alias E = const(SomeMap.ValueType);
2667         return *(cast(E*)&_table._store[_binIndex].value);
2668     }
2669     import core.internal.traits : Unqual;
2670     public LvalueElementRef!(Unqual!SomeMap) _elementRef;
2671     alias _elementRef this;
2672 }
2673 
2674 static private struct ByValue_rvalue(SomeMap)
2675 if (is(SomeMap == HybridHashMap!(_), _...) &&
2676     SomeMap.hasValue)
2677 {
2678     @property scope auto ref front() return @trusted // TODO: auto ref => ref V
2679     {
2680         version(D_Coverage) {} else pragma(inline, true);
2681         // TODO: functionize
2682         import std.traits : isMutable;
2683         static if (isMutable!(SomeMap)) // TODO: can this be solved without this `static if`?
2684             alias E = SomeMap.ValueType;
2685         else
2686             alias E = const(SomeMap.ValueType);
2687         return *(cast(E*)&_table._store[_binIndex].value);
2688     }
2689     import core.internal.traits : Unqual;
2690     public RvalueElementRef!(Unqual!SomeMap) _elementRef;
2691     alias _elementRef this;
2692 }
2693 
2694 /** Returns: range that iterates through the values of `c` in undefined order.
2695  */
2696 auto byValue(SomeMap)(auto ref return SomeMap c) @trusted
2697 if (is(SomeMap == HybridHashMap!(_), _...) &&
2698     SomeMap.hasValue)
2699 {
2700     import core.internal.traits : Unqual;
2701     import std.traits : isMutable;
2702     alias M = Unqual!SomeMap;
2703     alias C = const(SomeMap);
2704     static if (__traits(isRef, c)) // `c` is an l-value
2705         auto result = ByValue_lvalue!SomeMap((LvalueElementRef!(M)(cast(M*)&c)));
2706     else                        // `c` was is an r-value and can be moved
2707     {
2708         import core.lifetime : move;
2709         auto result = ByValue_rvalue!C((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret
2710     }
2711     result.findNextNonEmptyBin();
2712     return result;
2713 }
2714 
2715 static private struct ByKeyValue_lvalue(SomeMap)
2716 if (is(SomeMap == HybridHashMap!(_), _...) &&
2717     SomeMap.hasValue)
2718 {
2719     @property scope auto ref front() return @trusted // TODO: auto ref => ref T
2720     {
2721         version(D_Coverage) {} else pragma(inline, true);
2722         // TODO: functionize
2723         import std.traits : isMutable;
2724         static if (isMutable!(SomeMap))
2725             alias E = SomeMap.KeyValueType;
2726         else
2727             alias E = const(SomeMap.T);
2728         return *(cast(E*)&_table._store[_binIndex]);
2729     }
2730     import core.internal.traits : Unqual;
2731     public LvalueElementRef!(Unqual!SomeMap) _elementRef;
2732     alias _elementRef this;
2733 }
2734 
2735 /** Returns: range that iterates through the key-value-pairs of `c` in undefined order.
2736  */
2737 auto byKeyValue(SomeMap)(auto ref return SomeMap c) @trusted
2738 if (is(SomeMap == HybridHashMap!(_), _...) &&
2739     SomeMap.hasValue)
2740 {
2741     import core.internal.traits : Unqual;
2742     alias M = Unqual!SomeMap;
2743     static if (__traits(isRef, c)) // `c` is an l-value
2744         auto result = ByKeyValue_lvalue!SomeMap((LvalueElementRef!(M)(cast(M*)&c)));
2745     else                        // `c` was is an r-value and can be moved
2746     {
2747         import core.lifetime : move;
2748         auto result = ByKeyValue_rvalue!SomeMap((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret
2749     }
2750     result.findNextNonEmptyBin();
2751     return result;
2752 }
2753 
2754 /// make range from l-value and r-value. element access is always const
2755 @safe pure unittest
2756 {
2757     import core.exception : AssertError;
2758     import std.typecons : Nullable;
2759     import nxt.digestx.fnv : FNV;
2760     import nxt.array_help : s;
2761     debug import std.exception : assertThrown;
2762 
2763     import std.algorithm.searching : count;
2764     alias K = Nullable!(uint, uint.max);
2765     alias X = HybridHashSet!(K,
2766                            FNV!(64, true),
2767                            defaultKeyEqualPredOf!K,
2768                            Mallocator,
2769                            BorrowCheckFlag.yes);
2770 
2771     auto k11 = K(11);
2772     auto k22 = K(22);
2773     auto k33 = K(33);
2774     auto ks = [k11, k22, k33].s;
2775     auto k44 = K(44);
2776 
2777     // mutable
2778     auto x = X.withElements(ks);
2779     assert(!x.contains(k44));
2780     assert(!x.containsUsingLinearSearch(k44));
2781     assert(x.length == 3);
2782 
2783     assert(x.byElement.count == x.length);
2784     foreach (e; x.byElement)    // from l-value
2785     {
2786         debug static assert(is(typeof(e) == const(K))); // always const access
2787 
2788         // range invalidation forbidden:
2789         debug
2790         {
2791             assertThrown!AssertError(x.reserveExtra(1));  // range invalidation
2792             assertThrown!AssertError(x.clear());          // range invalidation
2793             assertThrown!AssertError(x.insert(k11));      // range invalidation
2794             assertThrown!AssertError(x.insertN([k11].s)); // range invalidation
2795             assertThrown!AssertError(x.remove(k11));      // range invalidation
2796         }
2797 
2798         // allowed
2799         assert(x.contains(e));
2800         assert(x.containsUsingLinearSearch(e));
2801 
2802         const eHit = e in x;
2803         assert(eHit);           // found
2804         assert(*eHit is e);     // and the value equals what we searched for
2805 
2806         const eDup = x.dup;     // duplication is `const` and allowed
2807         assert(eDup == x);
2808     }
2809 
2810     // const
2811     const y = X.withElements(ks);
2812     assert(!x.contains(k44));
2813     assert(!x.containsUsingLinearSearch(k44));
2814     foreach (e; y.byElement)    // from l-value
2815     {
2816         auto _ = y.byElement;   // ok to read-borrow again
2817         assert(y.contains(e));
2818         assert(y.containsUsingLinearSearch(e));
2819         debug static assert(is(typeof(e) == const(K)));
2820     }
2821 
2822     foreach (e; X.withElements([K(11)].s).byElement) // from r-value
2823     {
2824         assert(e == K(11));
2825         debug static assert(is(typeof(e) == const(K))); // always const access
2826     }
2827 }
2828 
2829 /// range checking
2830 @safe pure unittest
2831 {
2832     import core.exception : RangeError;
2833     import std.typecons : Nullable;
2834     import nxt.digestx.fnv : FNV;
2835     debug import std.exception : assertThrown, assertNotThrown;
2836     immutable n = 11;
2837 
2838     alias K = Nullable!(uint, uint.max);
2839     alias V = uint;
2840 
2841     alias X = HybridHashMap!(K, V, FNV!(64, true));
2842 
2843     auto s = X.withCapacity(n);
2844 
2845     void dummy(ref V value) {}
2846 
2847     debug assertThrown!RangeError(dummy(s[K(0)]));
2848 
2849     foreach (immutable i; 0 .. n)
2850     {
2851         const k = K(i);
2852         s[k] = V(i);
2853         debug assertNotThrown!RangeError(dummy(s[k]));
2854     }
2855 
2856     foreach (immutable i; 0 .. n)
2857     {
2858         const k = K(i);
2859         assert(s.remove(k));
2860         debug assertThrown!RangeError(dummy(s[k]));
2861     }
2862 
2863     s[K(0)] = V.init;
2864     auto vp = K(0) in s;
2865     debug static assert(is(typeof(vp) == V*));
2866     assert((*vp) == V.init);
2867 
2868     assert(s.remove(K(0)));
2869     assert(K(0) !in s);
2870 
2871     X t;
2872     t.reserveExtra(4096);
2873 
2874     t.clear();
2875 }
2876 
2877 /// class as value
2878 @safe pure unittest
2879 {
2880     import core.exception : RangeError;
2881     import std.typecons : Nullable;
2882     debug import std.exception : assertThrown, assertNotThrown;
2883     import nxt.digestx.fnv : FNV;
2884 
2885     immutable n = 11;
2886 
2887     alias K = Nullable!(uint, uint.max);
2888     class V
2889     {
2890         this(uint data) { this.data = data; }
2891         uint data;
2892     }
2893 
2894     alias X = HybridHashMap!(K, V, FNV!(64, true));
2895 
2896     auto s = X.withCapacity(n);
2897 
2898     void dummy(ref V value) {}
2899 
2900     debug assertThrown!RangeError(dummy(s[K(0)]));
2901 
2902     foreach (immutable i; 0 .. n)
2903     {
2904         const k = K(i);
2905         s[k] = new V(i);
2906         debug assertNotThrown!RangeError(dummy(s[k]));
2907     }
2908 
2909     // test range
2910     {
2911         auto sr = s.byKeyValue; // scoped range
2912         assert(sr.length == n);
2913         foreach (immutable i; 0 .. n)
2914         {
2915             sr.popFront();
2916             assert(sr.length == n - i - 1);
2917         }
2918     }
2919 
2920     foreach (immutable i; 0 .. n)
2921     {
2922         const k = K(i);
2923         assert(s.remove(k));
2924         debug assertThrown!RangeError(dummy(s[k]));
2925     }
2926 
2927     s[K(0)] = V.init;
2928     auto vp = K(0) in s;
2929     debug static assert(is(typeof(vp) == V*));
2930 
2931     assert(s.remove(K(0)));
2932     assert(K(0) !in s);
2933 
2934     X t;
2935     t.reserveExtra(4096);
2936 }
2937 
2938 /// constness inference of ranges
2939 pure nothrow unittest
2940 {
2941     import std.typecons : Nullable;
2942     import nxt.digestx.fnv : FNV;
2943 
2944     alias K = Nullable!(uint, uint.max);
2945     class V
2946     {
2947         this(uint data) { this.data = data; }
2948         uint data;
2949     }
2950 
2951     alias X = HybridHashMap!(K, V, FNV!(64, true));
2952     const x = X();
2953 
2954     foreach (const e; x.byKey)
2955     {
2956         debug static assert(is(typeof(e) == const(X.KeyType)));
2957     }
2958 
2959     foreach (const e; x.byValue)
2960     {
2961         debug static assert(is(typeof(e) == const(X.ValueType)));
2962     }
2963 
2964     foreach (const e; X.init.byValue)
2965     {
2966         debug static assert(is(typeof(e) == const(X.ValueType)));
2967     }
2968 
2969     foreach (const e; x.byKeyValue)
2970     {
2971         debug static assert(is(typeof(e.key) == const(X.KeyType)));
2972         debug static assert(is(typeof(e.value) == const(X.ValueType)));
2973         debug static assert(is(typeof(e) == const(X.ElementType)));
2974     }
2975 }
2976 
2977 /// range key constness and value mutability with `class` value
2978 pure nothrow unittest
2979 {
2980     import std.typecons : Nullable;
2981     import nxt.digestx.fnv : FNV;
2982 
2983     struct S
2984     {
2985         uint value;
2986     }
2987     alias K = Nullable!(S, S(uint.min)); // use uint.min to trigger use of faster `allocator.allocateZeroed`
2988 
2989     class V
2990     {
2991         this(uint data) { this.data = data; }
2992         uint data;
2993     }
2994 
2995     alias X = HybridHashMap!(K, V, FNV!(64, true));
2996     auto x = X();
2997 
2998     x[K(S(42))] = new V(43);
2999 
3000     assert(x.length == 1);
3001 
3002     foreach (e; x.byValue)      // `e` is auto ref
3003     {
3004         debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value
3005         assert(e.data == 43);
3006 
3007         // value mutation side effects
3008         e.data += 1;
3009         assert(e.data == 44);
3010         e.data -= 1;
3011         assert(e.data == 43);
3012     }
3013 
3014     foreach (ref e; x.byKeyValue)   // `e` is auto ref
3015     {
3016         debug static assert(is(typeof(e.key) == const(X.KeyType))); // const access to key
3017         debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value
3018 
3019         assert(e.key.value == 42);
3020         assert(e.value.data == 43);
3021 
3022         // key cannot be mutated
3023         debug static assert(!__traits(compiles, { e.key.value += 1; }));
3024 
3025         // value mutation side effects
3026         e.value.data += 1;
3027         assert(e.value.data == 44);
3028         e.value.data -= 1;
3029         assert(e.value.data == 43);
3030     }
3031 }
3032 
3033 /// range key constness and value mutability with `class` key and `class` value
3034 pure nothrow unittest
3035 {
3036     import nxt.digestx.fnv : FNV;
3037 
3038     class K
3039     {
3040         this(uint value)
3041         {
3042             this.value = value;
3043         }
3044 
3045         @property bool opEquals(const scope typeof(this) rhs) const
3046         {
3047             return value == rhs.value;
3048         }
3049 
3050         uint value;
3051     }
3052 
3053     class V
3054     {
3055         this(uint data) { this.data = data; }
3056         uint data;
3057     }
3058 
3059     alias X = HybridHashMap!(K, V, FNV!(64, true));
3060     auto x = X();
3061 
3062     x[new K(42)] = new V(43);
3063 
3064     assert(x.length == 1);
3065 
3066     foreach (e; x.byValue)      // `e` is auto ref
3067     {
3068         debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value
3069         assert(e.data == 43);
3070 
3071         // value mutation side effects
3072         e.data += 1;
3073         assert(e.data == 44);
3074         e.data -= 1;
3075         assert(e.data == 43);
3076     }
3077 
3078     foreach (ref e; x.byKeyValue)   // `e` is auto ref
3079     {
3080         debug static assert(is(typeof(e.key) == X.KeyType)); // mutable access to class key
3081         debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value
3082 
3083         assert(e.key.value == 42);
3084         assert(e.value.data == 43);
3085 
3086         // class key itself should not be mutable
3087         debug static assert(!__traits(compiles, { e.key = null; }));
3088 
3089         // members of key can be mutated
3090         debug static assert(__traits(compiles, { e.key.value += 1; }));
3091 
3092         // value mutation side effects
3093         e.value.data += 1;
3094         assert(e.value.data == 44);
3095         e.value.data -= 1;
3096         assert(e.value.data == 43);
3097     }
3098 }
3099 
3100 /// range key constness and value mutability with `class` key and `class` value
3101 pure nothrow unittest
3102 {
3103     import nxt.digestx.fnv : FNV;
3104     class K
3105     {
3106         this(uint value)
3107         {
3108             this.value = value;
3109         }
3110         uint value;
3111     }
3112 
3113     struct V
3114     {
3115         this(uint data) { this.data = data; }
3116         @disable this(this);
3117         uint data;
3118     }
3119 
3120     alias X = HybridHashMap!(K, V, FNV!(64, true));
3121     auto x = X();
3122 
3123     scope key42 = new K(42);
3124     x[key42] = V(43);
3125 
3126     assert(x.length == 1);
3127 
3128     foreach (ref e; x.byValue)  // `e` is auto ref
3129     {
3130         debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value
3131         assert(e.data == 43);
3132 
3133         // value mutation side effects
3134         e.data += 1;
3135         assert(e.data == 44);
3136         e.data -= 1;
3137         assert(e.data == 43);
3138     }
3139 
3140     foreach (ref e; x.byKeyValue) // `e` is auto ref
3141     {
3142         debug static assert(is(typeof(e.key) == X.KeyType)); // mutable access to class key
3143         debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value
3144 
3145         assert(e.key.value == 42);
3146         assert(e.value.data == 43);
3147 
3148         // value mutation side effects
3149         e.value.data += 1;
3150         assert(e.value.data == 44);
3151         e.value.data -= 1;
3152         assert(e.value.data == 43);
3153     }
3154 
3155     assert(x.length == 1);
3156 
3157     assert(x.remove(key42));
3158     assert(x.length == 0);
3159 
3160     x[key42] = V(43);
3161     assert(x.length == 1);
3162 }
3163 
3164 version(unittest)
3165 {
3166     T make(T)(ulong value)
3167     {
3168         static if (is(T == class))
3169             return new T(value);
3170         else
3171             return T(value);
3172     }
3173 }
3174 
3175 /// test various things
3176 @trusted unittest
3177 {
3178     import std.meta : AliasSeq;
3179     import std.typecons : Nullable;
3180     import std.algorithm.comparison : equal;
3181     import nxt.container.traits : mustAddGCRange;
3182     import nxt.digestx.fnv : FNV;
3183     import nxt.array_help : s;
3184 
3185     const n = 100;
3186 
3187     void testEmptyAll(K, V, X)(ref X x, size_t n,
3188                                scope K[] keys)
3189     {
3190         assert(x.length == n);
3191         foreach (key; keys)
3192         {
3193             static if (X.hasValue)
3194                 const element = X.ElementType(key, V.init);
3195             else
3196                 alias element = key;
3197 
3198             assert(x.length == n - key.get);
3199 
3200             const hitPtr = key in x;
3201             static if (X.hasValue)
3202                 assert(hitPtr && *hitPtr is element.value);
3203             else
3204                 assert(hitPtr && *hitPtr is element);
3205 
3206             assert(x.remove(key));
3207             assert(x.length == n - key.get - 1);
3208 
3209             static if (!X.hasValue)
3210             {
3211                 assert(!x.contains(key));
3212                 assert(!x.containsUsingLinearSearch(key));
3213             }
3214             assert(key !in x);
3215             assert(!x.remove(key));
3216             assert(x.length == n - key.get - 1);
3217         }
3218 
3219         assert(x.length == 0);
3220 
3221         x.clear();
3222         assert(x.length == 0);
3223     }
3224 
3225     X testDup(X)(scope ref X x, size_t n)
3226     {
3227         typeof(return) y = x.dup;
3228 
3229         assert(x._store.ptr !is y._store.ptr);
3230         assert(x.length == y.length);
3231         assert(y.length == n);
3232         // non-symmetric algorithm so both are needed
3233         assert(y == x);
3234         assert(x == y);
3235 
3236         static if (X.hasValue)
3237         {
3238             assert(equal(x.byKey,
3239                          y.byKey));
3240             assert(equal(x.byValue,
3241                          y.byValue));
3242             auto a = x.byKeyValue;
3243             auto b = y.byKeyValue;
3244             size_t i = 0;
3245             while (!a.empty &&
3246                    !b.empty)
3247             {
3248                 a.popFront();
3249                 b.popFront();
3250                 i++;
3251             }
3252 			auto xR = x.byKeyValue;
3253 			auto yR = y.byKeyValue;
3254 			assert(xR.length == yR.length);
3255 			size_t ix = 0;
3256 			while (!xR.empty &&
3257 				   !yR.empty)
3258 			{
3259 				auto xK = xR.front.key;
3260 				auto yK = yR.front.key;
3261 				auto xV = xR.front.value;
3262 				auto yV = yR.front.value;
3263 				// import std.stdio : writeln;
3264 				// writeln("ix:", ix, " xV:", xV, " yV:", yV);
3265 				assert(xK == yK);
3266 				assert(xV == yV);
3267 				assert(xR.front == yR.front);
3268 				xR.popFront();
3269 				yR.popFront();
3270 				ix++;
3271 			}
3272             assert(equal(x.byKeyValue,
3273                          y.byKeyValue));
3274         }
3275         else
3276             assert(equal(x.byElement,
3277                          y.byElement));
3278 
3279         debug static assert(!__traits(compiles, { const _ = x < y; })); // no ordering
3280 
3281         return y;
3282     }
3283 
3284     alias NullableUlong = Nullable!(ulong, ulong.max);
3285 
3286     static class SomeSimpleClass
3287     {
3288         @safe pure nothrow @nogc
3289         this(ulong value)
3290         {
3291             this._value = value;
3292         }
3293 
3294         @safe pure nothrow @nogc
3295         ulong get() const
3296         {
3297             return _value;
3298         }
3299 
3300         void toString(Sink)(ref scope Sink sink) const
3301         {
3302             import std.format : formattedWrite;
3303             sink.formattedWrite(typeof(this).stringof, "(%s)", _value);
3304         }
3305 
3306         @property bool opEquals(const scope typeof(this) rhs) const
3307         {
3308             return _value == rhs._value;
3309         }
3310 
3311         private ulong _value;
3312     }
3313 
3314     debug static assert(mustAddGCRange!string);
3315 
3316     foreach (K; AliasSeq!(SomeSimpleClass,
3317                           NullableUlong))
3318     {
3319         foreach (V; AliasSeq!(string, int, void))
3320         {
3321             alias X = HybridHashMap!(K, V, FNV!(64, true));
3322 
3323             static if (!X.hasValue)
3324             {
3325                 auto k11 = make!K(11);
3326                 auto k12 = make!K(12);
3327                 auto k13 = make!K(13);
3328 
3329                 auto x = X.withElements([k11, k12, k13].s);
3330 
3331                 import std.algorithm : count;
3332 
3333                 // ByLvalueElement
3334                 auto xr = x.byElement;
3335 
3336                 alias R = typeof(xr);
3337                 import std.range.primitives : isInputRange;
3338                 import std.traits : ReturnType;
3339                 debug static assert(is(typeof(R.init) == R));
3340                 debug static assert(is(ReturnType!((R xr) => xr.empty) == bool));
3341 
3342                 // TODO: Is this needed? debug static assert(!__traits(compiles, { xr.front == K.init; })); // always head-const
3343                 auto f = xr.front;
3344                 static if (is(K == class))
3345                 {
3346                     debug static assert(is(typeof(f) == K)); // tail-mutable
3347                 }
3348                 else
3349                 {
3350                     debug static assert(is(typeof(f) == const(K))); // tail-const
3351                 }
3352 
3353                 debug static assert(is(typeof((R xr) => xr.front)));
3354                 debug static assert(!is(ReturnType!((R xr) => xr.front) == void));
3355                 debug static assert(is(typeof((R xr) => xr.popFront)));
3356 
3357                 debug static assert(isInputRange!(typeof(xr)));
3358 
3359                 assert(x.byElement.count == 3);
3360 
3361                 X y;
3362                 size_t ix = 0;
3363                 foreach (ref e; x.byElement)
3364                 {
3365                     assert(x.contains(e));
3366                     assert(x.containsUsingLinearSearch(e));
3367                     assert(!y.contains(e));
3368                     assert(!y.containsUsingLinearSearch(e));
3369                     static if (is(K == class))
3370                         y.insert(cast(K)e); // ugly but ok in tests
3371                     else
3372                         y.insert(e);
3373                     assert(y.contains(e));
3374                     assert(y.containsUsingLinearSearch(e));
3375                     ix++;
3376                 }
3377 
3378                 assert(y.byElement.count == 3);
3379                 assert(x == y);
3380 
3381                 const z = X();
3382                 assert(z.byElement.count == 0);
3383 
3384                 immutable w = X();
3385                 assert(w.byElement.count == 0);
3386 
3387                 {
3388                     auto xc = X.withElements([k11, k12, k13].s);
3389                     assert(xc.length == 3);
3390                     assert(xc.contains(k11));
3391                     assert(xc.containsUsingLinearSearch(k11));
3392 
3393                     // TODO: http://forum.dlang.org/post/kvwrktmameivubnaifdx@forum.dlang.org
3394                     xc.removeAllMatching!(_ => _ == k11);
3395 
3396                     assert(xc.length == 2);
3397                     assert(!xc.contains(k11));
3398                     assert(!xc.containsUsingLinearSearch(k11));
3399 
3400                     xc.removeAllMatching!(_ => _ == k12);
3401                     assert(!xc.contains(k12));
3402                     assert(!xc.containsUsingLinearSearch(k12));
3403                     assert(xc.length == 1);
3404 
3405                     xc.removeAllMatching!(_ => _ == k13);
3406                     assert(!xc.contains(k13));
3407                     assert(!xc.containsUsingLinearSearch(k13));
3408                     assert(xc.length == 0);
3409 
3410                     // this is ok
3411                     foreach (_; xc.byElement) {}
3412                 }
3413 
3414                 {               // ByRvalueElement
3415                     auto k = X.withElements([k11, k12].s).filtered!(_ => _ != k11).byElement;
3416                     debug static assert(isInputRange!(typeof(k)));
3417                     assert(k.front == k12);
3418 
3419                     debug static assert(!__traits(compiles, { k.front = K.init; })); // head-const
3420                     static if (is(K == class))
3421                     {
3422                         debug static assert(is(typeof(k.front) == K)); // tail-mutable
3423                     }
3424                     else
3425                     {
3426                         debug static assert(is(typeof(k.front) == const(K))); // tail-const
3427                     }
3428 
3429                     k.popFront();
3430                     assert(k.empty);
3431                 }
3432 
3433                 {
3434                     X q;
3435                     auto qv = [make!K(11U), make!K(12U), make!K(13U), make!K(14U)].s;
3436                     q.insertN(qv[]);
3437                     foreach (e; qv[])
3438                     {
3439                         assert(q.contains(e));
3440                         assert(q.containsUsingLinearSearch(e));
3441                     }
3442                     q.clear();
3443                     assert(q.empty);
3444                 }
3445             }
3446 
3447             static if (is(V == string))
3448             {
3449                 debug static assert(mustAddGCRange!V);
3450                 debug static assert(mustAddGCRange!(V[1]));
3451                 debug static assert(mustAddGCRange!(X.T));
3452             }
3453 
3454             auto x1 = X();            // start empty
3455 
3456             // fill x1
3457 
3458             import std.array : Appender;
3459             Appender!(K[]) keys;
3460 
3461             foreach (immutable key_; 0 .. n)
3462             {
3463                 auto key = make!K(key_);
3464                 keys.put(key);
3465 
3466                 // create elements
3467                 static if (X.hasValue)
3468                 {
3469                     auto value = V.init;
3470                     auto element = X.ElementType(key, value);
3471                 }
3472                 else
3473                     // no assignment because Nullable.opAssign may leave rhs in null state
3474                     auto element = key;
3475 
3476                 assert(key !in x1);
3477 
3478                 assert(x1.length == key.get);
3479                 assert(x1.insert(element) == X.InsertionStatus.added);
3480                 assert(x1.length == key.get + 1);
3481 
3482                 static if (X.hasValue)
3483                 {
3484                     import std.conv : to;
3485                     auto e2 = X.ElementType(key, (42 + key_).to!V);
3486                     assert(x1.insert(e2) == X.InsertionStatus.modified);
3487                     assert(x1.contains(key));
3488                     assert(x1.get(key, V.init) == (42 + key_).to!V);
3489 
3490                     assert(x1.remove(key));
3491                     assert(!x1.contains(key));
3492 
3493                     x1[key] = value; // restore value
3494                     assert(x1.contains(key));
3495                 }
3496 
3497                 assert(x1.length == key.get + 1);
3498 
3499                 const hitPtr = key in x1;
3500                 static if (X.hasValue)
3501                     assert(hitPtr && *hitPtr == value);
3502                 else
3503                     assert(hitPtr && *hitPtr is key);
3504 
3505                 auto status = x1.insert(element);
3506                 assert(status == X.InsertionStatus.unmodified);
3507                 static if (X.hasValue)
3508                     assert(x1.insert(key, value) == X.InsertionStatus.unmodified);
3509                 assert(x1.length == key.get + 1);
3510 
3511                 assert(key in x1);
3512             }
3513 
3514             static if (X.hasValue)
3515             {
3516                 import nxt.container.dynamic_array : Array = DynamicArray;
3517                 Array!(X.ElementType) a1; // remember the keys
3518 
3519                 foreach (const ref key; x1.byKey)
3520                 {
3521                     auto keyPtr = key in x1;
3522                     assert(keyPtr);
3523                     a1 ~= X.ElementType(cast(K)key, (*keyPtr));
3524                 }
3525 
3526                 assert(x1.length == a1.length);
3527 
3528                 foreach (ae; a1[])
3529                 {
3530                     auto keyPtr = ae.key in x1;
3531                     assert(keyPtr);
3532                     assert((*keyPtr) is ae.value);
3533                 }
3534             }
3535 
3536             assert(x1.length == n);
3537 
3538             auto x2 = testDup(x1, n);
3539 
3540             testEmptyAll!(K, V)(x1, n, keys.data);
3541 
3542             testEmptyAll!(K, V)(x2, n, keys.data); // should be not affected by emptying of x1
3543         }
3544     }
3545 }
3546 
3547 ///
3548 @safe pure nothrow @nogc unittest
3549 {
3550     import std.typecons : Nullable;
3551     import nxt.digestx.fnv : FNV;
3552 
3553     alias X = HybridHashMap!(Nullable!(size_t, size_t.max), size_t, FNV!(64, true));
3554     X x;
3555     assert(x.empty);
3556     // import nxt.container.dynamic_array : Array = DynamicArray;
3557     // TODO: these segfault:
3558     // TODO: auto a = Array!(X.KeyType).withElementsOfRange_untested(x.byKey); // l-value byKey
3559     // TODO: auto b = Array!(X.KeyType).withElementsOfRange_untested(X().byKey); // r-value byKey
3560 }
3561 
3562 /// manual Nullable type
3563 @safe pure unittest
3564 {
3565     import nxt.digestx.fnv : FNV;
3566 
3567     static class Zing
3568     {
3569         @safe pure nothrow @nogc:
3570         this(ulong value) { this._value = value; }
3571         private ulong _value;
3572     }
3573     debug static assert(isNullable!Zing);
3574 
3575     enum Alt
3576     {
3577         unknown,
3578         a,
3579         b,
3580         c,
3581         d
3582     }
3583 
3584     struct ZingRelation
3585     {
3586         Zing zing;
3587         Alt alts;
3588 
3589         alias nullifier = zing;
3590         static immutable nullValue = typeof(this).init;
3591 
3592         bool opEquals(const scope typeof(this) that) const @safe pure nothrow @nogc
3593         {
3594             return (this.zing is that.zing &&
3595                     this.alts == that.alts);
3596         }
3597     }
3598     debug static assert(isNullable!ZingRelation);
3599 
3600     alias X = HybridHashSet!(ZingRelation, FNV!(64, true));
3601     debug static assert(X.sizeof == 32); // TODO: fix hole handling and change to 24
3602     X x;
3603 
3604     scope e = ZingRelation(new Zing(42), Alt.init);
3605 
3606     assert(!x.contains(e));
3607     assert(!x.containsUsingLinearSearch(e));
3608     assert(x.insert(e) == X.InsertionStatus.added);
3609     assert(x.contains(e));
3610     assert(x.containsUsingLinearSearch(e));
3611 }
3612 
3613 /// abstract class value type
3614 @safe unittest
3615 {
3616     static abstract class Zing
3617     {
3618         @safe pure nothrow @nogc:
3619     }
3620     static class Node : Zing
3621     {
3622         @safe pure nothrow @nogc:
3623     }
3624 
3625     alias X = HybridHashSet!(Zing);
3626     X x;
3627 
3628     const Zing cz = new Node();
3629     x.insert(cz);               // ok to insert const
3630 
3631     Zing z = new Node();
3632     x.insert(z); // ok to insert mutable because hashing is on address by default
3633 }
3634 
3635 /// class type with default hashing
3636 @safe unittest
3637 {
3638     static class Base
3639     {
3640         static size_t dtorCount = 0; // number of calls to this destructor
3641     @safe nothrow @nogc:
3642         ~this() nothrow @nogc { dtorCount += 1; }
3643     pure:
3644         this(ulong value) { this._value = value; }
3645         @property bool opEquals(const scope typeof(this) rhs) const
3646         {
3647             return _value == rhs._value;
3648         }
3649         override hash_t toHash() const
3650         {
3651             return hashOf(_value);
3652         }
3653         private ulong _value;
3654     }
3655 
3656     /** Node containing same data members but different type. */
3657     static class Node : Base
3658     {
3659         @safe pure nothrow @nogc:
3660         this(ulong value) { super(value);  }
3661     }
3662     debug static assert(is(Node : Base));
3663 
3664     import nxt.hash_functions : hashOfPolymorphic; // neede to separate hash of `Base(N)` from `Node(N)`
3665     alias X = HybridHashSet!(Base, hashOfPolymorphic, "a && b && (typeid(a) is typeid(b)) && a.opEquals(b)");
3666     debug static assert(X.sizeof == 24);
3667     X x;
3668 
3669     // top-class
3670     scope b42 = new Base(42);
3671     assert(!x.contains(b42));
3672     assert(!x.containsUsingLinearSearch(b42));
3673     assert(x.insert(b42) == X.InsertionStatus.added);
3674     assert(x.contains(b42));
3675     assert(x.containsUsingLinearSearch(b42));
3676     assert(x.tryGetElementFromCtorParams!Base(42) !is null);
3677     assert(Base.dtorCount == 1);
3678     assert(x.tryGetElementFromCtorParams!Base(42)._value == 42);
3679     assert(Base.dtorCount == 2);
3680     assert(x.tryGetElementFromCtorParams!Base(41) is null);
3681     assert(Base.dtorCount == 3);
3682 
3683     // top-class
3684     scope b43 = new Base(43);
3685     assert(!x.contains(b43));
3686     assert(!x.containsUsingLinearSearch(b43));
3687     assert(x.insert(b43) == X.InsertionStatus.added);
3688     assert(x.contains(b43));
3689     assert(x.containsUsingLinearSearch(b43));
3690     assert(x.tryGetElementFromCtorParams!Base(43) !is null);
3691     assert(Base.dtorCount == 4);
3692     assert(x.tryGetElementFromCtorParams!Base(43)._value == 43);
3693     assert(Base.dtorCount == 5);
3694 
3695     // sub-class
3696     assert(x.tryGetElementFromCtorParams!Node(42) is null);
3697     assert(Base.dtorCount == 6);
3698     immutable n42 = new Node(42);
3699     assert(!x.contains(n42));     // mustn't equal to `b42`
3700     assert(!x.containsUsingLinearSearch(n42)); // mustn't equal to `b42`
3701     assert(x.insert(n42) == X.InsertionStatus.added); // added as separate type
3702     assert(x.contains(n42));
3703     assert(x.containsUsingLinearSearch(n42));
3704     assert(x.tryGetElementFromCtorParams!Node(42) !is null);
3705     assert(Base.dtorCount == 7);
3706     assert(x.tryGetElementFromCtorParams!Node(42)._value == 42);
3707     assert(Base.dtorCount == 8);
3708 
3709     assert(hashOf(b42) == hashOf(n42));
3710 
3711     // sub-class
3712     assert(x.tryGetElementFromCtorParams!Node(43) is null);
3713     assert(Base.dtorCount == 9);
3714     auto n43 = new Node(43);
3715     assert(!x.contains(n43));     // mustn't equal to `b43`
3716     assert(!x.containsUsingLinearSearch(n43)); // mustn't equal to `b43`
3717     assert(x.insert(n43) == X.InsertionStatus.added); // added as separate type
3718     assert(x.contains(n43));
3719     assert(x.containsUsingLinearSearch(n43));
3720     assert(x.tryGetElementFromCtorParams!Node(43) !is null);
3721     assert(Base.dtorCount == 10);
3722     assert(x.tryGetElementFromCtorParams!Node(43)._value == 43);
3723     assert(Base.dtorCount == 11);
3724 
3725     assert(hashOf(b43) == hashOf(n43));
3726 }
3727 
3728 /// enumeration key
3729 @safe pure unittest
3730 {
3731     import nxt.digestx.fnv : FNV;
3732 
3733     enum Alt
3734     {
3735         nullValue,              // trait
3736         a, b, c, d
3737     }
3738     alias X = HybridHashSet!(Alt, FNV!(64, true));
3739     X x;
3740     assert(!x.contains(Alt.a));
3741 
3742     assert(x.insert(Alt.a) == X.InsertionStatus.added);
3743 
3744     assert(x.contains(Alt.a));
3745     assert(x.containsUsingLinearSearch(Alt.a));
3746     assert(!x.contains(Alt.b));
3747     assert(!x.contains(Alt.c));
3748     assert(!x.contains(Alt.d));
3749     assert(!x.containsUsingLinearSearch(Alt.b));
3750     assert(!x.containsUsingLinearSearch(Alt.c));
3751     assert(!x.containsUsingLinearSearch(Alt.d));
3752 
3753     assert(x.remove(Alt.a));
3754     assert(!x.contains(Alt.a));
3755     assert(!x.containsUsingLinearSearch(Alt.a));
3756 }
3757 
3758 ///
3759 @safe pure nothrow unittest
3760 {
3761     import nxt.digestx.fnv : FNV;
3762     static struct Rel
3763     {
3764         static immutable nullValue = typeof(this).init;
3765         string name;            // relation name. WARNING compiler crashes when qualified with `package`
3766     }
3767     alias X = HybridHashSet!(Rel, FNV!(64, true));
3768     X x;
3769     foreach (const i; 0 .. 100)
3770     {
3771         const char[1] ch = ['a' + i];
3772         assert(!x.contains(Rel(ch.idup)));
3773         assert(!x.containsUsingLinearSearch(Rel(ch.idup)));
3774         x.insert(Rel(ch.idup));
3775         assert(x.contains(Rel(ch.idup)));
3776         /* TODO: assert(x.containsUsingLinearSearch(Rel(ch.idup))); */
3777     }
3778 }
3779 
3780 /// `SSOString` as set key type
3781 @safe pure nothrow @nogc unittest
3782 {
3783     import nxt.sso_string : SSOString;
3784     import nxt.digestx.fnv : FNV;
3785 
3786     alias K = SSOString;
3787     static assert(isHoleable!K);
3788     alias X = HybridHashSet!(K, FNV!(64, true));
3789     const n = 100;
3790 
3791     X a;
3792     foreach (const i; 0 .. n)
3793     {
3794         const char[1] ch = ['a' + i];
3795         const k = K(ch);        // @nogc
3796 
3797         assert(!a.contains(k));
3798         assert(!a.containsUsingLinearSearch(k));
3799 
3800         assert(a.insert(K(ch)) == X.InsertionStatus.added);
3801         // TODO: assert(a.insertAndReturnElement(K(ch)) == k);
3802         assert(a.contains(k));
3803         assert(a.containsUsingLinearSearch(k));
3804 
3805         assert(a.remove(k));
3806         assert(!a.contains(k));
3807         assert(a.insert(K(ch)) == X.InsertionStatus.added);
3808 
3809         assert(a.remove(ch[]));
3810         assert(!a.contains(k));
3811         assert(a.insert(K(ch)) == X.InsertionStatus.added);
3812     }
3813 
3814     X b;
3815     foreach (const i; 0 .. n)
3816     {
3817         const char[1] ch = ['a' + (n - 1 - i)];
3818         const k = K(ch);        // @nogc
3819 
3820         assert(!b.contains(k));
3821         assert(!b.containsUsingLinearSearch(k));
3822 
3823         assert(b.insert(K(ch)) == X.InsertionStatus.added);
3824         // TODO: assert(b.insertAndReturnElement(K(ch)) == k);
3825 
3826         assert(b.contains(k));
3827         assert(b.containsUsingLinearSearch(k));
3828 
3829         assert(b.remove(k));
3830         assert(!b.contains(k));
3831 
3832         assert(b.insert(K(ch)) == X.InsertionStatus.added);
3833     }
3834 
3835     assert(a == b);
3836 
3837     const us = K("_");
3838     assert(!a.contains(us));
3839     a ~= us;
3840     assert(a.contains(us));
3841 }
3842 
3843 /// test `opIndexOpAssign`
3844 @safe pure nothrow unittest
3845 {
3846     import nxt.sso_string : SSOString;
3847     import nxt.digestx.fnv : FNV;
3848 
3849     alias K = SSOString;
3850     alias V = long;
3851     alias X = HybridHashMap!(K, V, FNV!(64, true));
3852 
3853     X x;
3854 
3855     const a = K("a");
3856     const b = K("b");
3857 
3858     x[a] = 17;
3859     assert(x[a] == 17);
3860 
3861     x[a] += 10;                 // opIndexOpAssign!("+=") with existing key
3862     assert(x[a] == 27);
3863 
3864     x[b] += 10;                 // opIndexOpAssign!("+=") with non-existing key
3865     assert(x[b] == 10);
3866 
3867     x[b] *= 10;                 // opIndexOpAssign!("*=") with non-existing key
3868     assert(x[b] == 100);
3869 
3870     assert(x.length == 2);
3871 
3872     assert(x.contains(a));
3873     assert(x.contains(a[]));
3874     assert(a in x);
3875     assert(a[] in x);
3876 
3877     assert(x.contains(b));
3878     assert(x.contains(b[]));
3879     assert(b in x);
3880     assert(b[] in x);
3881 
3882     const c = K("c");
3883     assert(!x.contains(c));
3884     assert(!x.contains(c[]));
3885     assert(c !in x);
3886     assert(c[] !in x);
3887 }
3888 
3889 /// use prime numbers as capacity
3890 version(none)					// TODO: enable
3891 @safe pure unittest
3892 {
3893     import nxt.address : AlignedAddress;
3894     alias K = AlignedAddress!1;
3895     alias V = size_t;
3896     alias M = HybridHashMap!(K, V, hashOf, defaultKeyEqualPredOf!K, Mallocator,
3897 							 BorrowCheckFlag.no, true, PrimeCapacityFlag.yes);
3898     M x;
3899     assert(x.empty);
3900 }
3901 
3902 /// `SSOString` as map key type
3903 @safe pure nothrow @nogc unittest
3904 {
3905     import nxt.sso_string : SSOString;
3906     import nxt.digestx.fnv : FNV;
3907     alias K = SSOString;
3908     alias V = long;
3909     alias X = HybridHashMap!(K, V, FNV!(64, true));
3910     const n = 100;
3911 
3912     immutable default_k = K("miss");
3913 
3914     X a;
3915 
3916     // insert all
3917     foreach (const i; 0 .. n)
3918     {
3919         const char[1] ch = ['a' + i];
3920         const k = K(ch);        // @nogc
3921         assert(k[] == ch[]);
3922 
3923         assert(!a.contains(k));
3924         assert(!a.contains(ch[]));                          // @nogc
3925         assert(a.getKeyRef(k, default_k)[] is default_k[]); // on miss use `default_k`
3926         // TODO: assert(a.getKeyRef(ch, default_k)[] is default_k[]); // on miss use `default_k`
3927 
3928         a[k] = V.init;
3929 
3930         assert(a.contains(k));
3931         assert(a.contains(ch[]));                    // @nogc
3932         assert(a.getKeyRef(k, default_k)[] !is k[]); // on hit doesn't use `default_k`
3933         assert(a.getKeyRef(k, default_k)[] == ch);
3934         // TODO: assert(a.getKeyRef(ch, default_k)[] !is k[]); // on hit doesn't use `default_k`
3935         // assert(a.getKeyRef(ch, default_k)[] == ch);
3936     }
3937     assert(a.length == n);
3938 
3939     // remove all
3940     foreach (const i; 0 .. n)
3941     {
3942         const char[1] ch = ['a' + i];
3943         const k = K(ch);        // @nogc
3944         assert(a.contains(k));
3945         assert(a.remove(k));
3946         assert(!a.contains(k));
3947     }
3948     assert(a.length == 0);
3949 
3950     // insert all again
3951     foreach (const i; 0 .. n)
3952     {
3953         const char[1] ch = ['a' + i];
3954         const k = K(ch);        // @nogc
3955         assert(k[] == ch[]);
3956 
3957         assert(!a.contains(k));
3958         assert(!a.contains(ch[]));                          // @nogc
3959         assert(a.getKeyRef(k, default_k)[] is default_k[]); // on miss use `default_k`
3960         // TODO: assert(a.getKeyRef(ch, default_k)[] is default_k[]); // on miss use `default_k`
3961 
3962         a[k] = V.init;
3963     }
3964     assert(a.length == n);
3965 
3966     X b;
3967     foreach (const i; 0 .. n)
3968     {
3969         const char[1] ch = ['a' + (n - 1 - i)];
3970         const k = K(ch);        // @nogc
3971 
3972         assert(!b.contains(k));
3973 
3974         b[k] = V.init;
3975 
3976         assert(b.contains(k));
3977     }
3978 
3979     assert(a == b);
3980 }
3981 
3982 ///
3983 @safe pure nothrow @nogc unittest
3984 {
3985     import nxt.address : AlignedAddress;
3986 	alias A = AlignedAddress!1;
3987     HybridHashMap!(A, A) m;
3988     static assert(m.sizeof == 3*size_t.sizeof); // assure that hole bitmap is not used
3989     foreach (const address; 1 .. 0x1000)
3990     {
3991         const key = address;
3992         const value = 2*address;
3993         assert(A(key) !in m);
3994         m[A(key)] = A(value);
3995 		const eq = m[A(key)] == A(value);
3996         assert(eq);
3997         assert(A(key) in m);
3998     }
3999 }
4000 
4001 ///
4002 @safe pure nothrow @nogc unittest
4003 {
4004     import nxt.sso_string : SSOString;
4005     alias K = SSOString;
4006     alias V = long;
4007     alias X = HybridHashMap!(K, V, hashOf, defaultKeyEqualPredOf!(K), Mallocator, BorrowCheckFlag.no);
4008 	X x;
4009 }
4010 
4011 /** Is `true` iff `T` is a memory address (either a `class` or a pointer). */
4012 enum bool isAddress(T) = (is(T == class) ||
4013                           (is(T == U*, U) &&
4014                            // exclude alias this:
4015                            !(is(T == struct) ||
4016                              is(T == union) ||
4017                              is(T == interface))));
4018 
4019 /** Is `true` iff `T` has a specific value dedicated to representing holes
4020  * (removed/erase) values.
4021  */
4022 enum isHoleable(T) = (// __traits(hasMember, T, "isHole") &&
4023                       // __traits(hasMember, T, "holeify") &&
4024     __traits(hasMember, T, "holeValue"));
4025 
4026 /** Default key equality/equivalence predicate for the type `T`.
4027  */
4028 template defaultKeyEqualPredOf(T)
4029 {
4030     static if (is(T == class))
4031         // static assert(__traits(hasMember, T, "opEquals"),
4032         //               "Type" ~ T.stringof ~ " doesn't have local opEquals() defined");
4033         // enum defaultKeyEqualPredOf = "a && b && a.opEquals(b)";
4034         enum defaultKeyEqualPredOf = "a is b";
4035         // (const T a, const T b) => ((a !is null) && (b !is null) && a.opEquals(b));
4036     else
4037         enum defaultKeyEqualPredOf = "a == b";
4038 }
4039 
4040 ///
4041 @safe pure nothrow unittest
4042 {
4043     class C
4044     {
4045         @safe pure nothrow @nogc:
4046         this(int x)
4047         {
4048             this.x = x;
4049         }
4050         @property bool opEquals(const scope typeof(this) rhs) const
4051         {
4052             return x == rhs.x;
4053         }
4054         @property override bool opEquals(const scope Object rhs) const @trusted
4055         {
4056             C rhs_ = cast(C)rhs;
4057             return rhs_ && x == rhs_.x;
4058         }
4059         int x;
4060     }
4061     static assert(defaultKeyEqualPredOf!(C) == "a is b");
4062 }