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