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