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