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