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