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, __FILE__, "(", __LINE__, ",1): Debug: ", K.stringof, " => ", V);
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 && __traits(isCopyable, T)) /+ TODO: support uncopyable T? +/ {
712 		static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
713 		foreach (ref element; elements)
714 			insert(element);
715 	}
716 
717 	/// Is `true` iff in-place rehashing during growth should be performed.
718 	enum bool growInPlaceFlag = false; /+ TODO: warning `growInPlaceWithCapacity` is buggy +/
719 
720 	/// Numerator for grow scale.
721 	enum growScaleP = 3;
722 	/// Denominator for grow scale.
723 	enum growScaleQ = 2;
724 
725 	/** Reserve room for `extraCapacity` number of extra buckets. */
726 	void reserveExtra(in size_t extraCapacity) { /*!tlm*/
727 		version (LDC) pragma(inline, true);
728 		static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
729 		immutable newCapacity = (_count + extraCapacity)*growScaleP/growScaleQ;
730 		if (newCapacity > _store.elts.length)
731 			growWithNewCapacity(newCapacity);
732 	}
733 
734 	/// Grow (rehash) to make for `newCapacity` number of elements.
735 	private void growWithNewCapacity()(in size_t newCapacity) /*tlm*/ {
736 		version (unittest) assert(newCapacity > _store.elts.length);
737 		static if (__traits(hasMember, Allocator, "reallocate")) {
738 			static if (growInPlaceFlag)
739 				growInPlaceWithCapacity(newCapacity);
740 			else
741 				growStandardWithNewCapacity(newCapacity);
742 		} else
743 			growStandardWithNewCapacity(newCapacity);
744 	}
745 
746 	/// Grow (rehash) store to make room for `newCapacity` number of elements.
747 	private void growStandardWithNewCapacity()(in size_t newCapacity) /*tlm*/ {
748 		version (unittest) assert(newCapacity > _store.elts.length);
749 		auto next = typeof(this).withCapacity(newCapacity);
750 		foreach (immutable i, ref bin; _store.elts)
751 			if (isOccupiedAtIndex(i)) {
752 				next.insertMoveWithoutGrowth(bin); // value is zeroed but
753 				static if (!hasHoleableKey)
754 					keyOf(bin).nullify(); // keyC must zeroed
755 			}
756 		move(next, this);
757 	}
758 
759 	/// Tag as lazily delete element at index `index`.`
760 	private void tagAsLazilyDeletedElementAtIndex(in size_t index) {
761 		version (LDC) pragma(inline, true);
762 		// key
763 		static if (options.linearSearchMaxSize != 0)
764 			if (_store.elts.length * T.sizeof <= options.linearSearchMaxSize) {
765 				keyOf(_store.elts[index]).nullify();
766 				goto done;
767 			}
768 		static if (hasHoleableKey)
769 			keyOf(_store.elts[index]) = holeKeyConstant;
770 		else {
771 			keyOf(_store.elts[index]).nullify();
772 			tagHoleAtIndex(index);
773 		}
774 	done:
775 		// value
776 		static if (hasValue) {
777 			static if (hasElaborateDestructor!V) // if we should clear all
778 				.destroy(valueOf(_store.elts[index]));
779 			static if (mustAddGCRange!V) // if we should clear all
780 				valueOf(_store.elts[index]) = V.init; // avoid GC mark-phase dereference
781 		}
782 	}
783 
784 	/// Insert `element` at `index`.
785 	private void insertElementAtIndex(SomeElement)(scope SomeElement element, in size_t index) @trusted /*tlm*/ {
786 		version (LDC) pragma(inline, true);
787 		static if (isSlice!SomeElement &&
788 				   !is(typeof(SomeElement.init[0]) == immutable)) {
789 			/* key is an array of non-`immutable` elements which cannot safely
790 			 * be stored because keys must be immutable for hashing to work
791 			 * properly, therefore duplicate */
792 			keyOf(_store.elts[index]) = element.idup;
793 		} else {
794 			static if (__traits(isPOD, SomeElement))
795 				_store.elts[index] = element;
796 			else {
797 				static if (__traits(isPOD, K))
798 					keyOf(_store.elts[index]) = keyOf(element);
799 				else
800 					move(keyOf(element),
801 						 keyOf(_store.elts[index]));
802 
803 				static if (hasValue) {
804 					import core.lifetime : moveEmplace;
805 					moveEmplace(valueOf(element),
806 								valueOf(_store.elts[index]));
807 				}
808 			}
809 		}
810 	}
811 
812 	/// Rehash elements in-place.
813 	private void rehashInPlace()() @trusted /*tlm*/ {
814 		import core.bitop : bts, bt;
815 		import nxt.array_help : makeBitArrayZeroed, wordCountOfBitCount;
816 
817 		size_t* dones = makeBitArrayZeroed!Allocator(_store.elts.length);
818 
819 		foreach (const doneIndex; 0 .. _store.elts.length) {
820 			if (bt(dones, doneIndex)) { continue; } // if _store.elts[doneIndex] continue
821 			if (isOccupiedAtIndex(doneIndex)) {
822 				import core.lifetime : moveEmplace;
823 				T currentElement = void;
824 
825 				/+ TODO: functionize: +/
826 				moveEmplace(_store.elts[doneIndex], currentElement);
827 				static if (is(K == Nullable!(_), _))
828 					keyOf(_store.elts[doneIndex]).nullify(); // `moveEmplace` doesn't init source of type Nullable
829 
830 				while (true) {
831 					// TODO remove param `element`
832 					alias pred = (in index, in element)
833 						=> (!isOccupiedAtIndex(index) || // free slot
834 							!bt(dones, index)); // or a not yet replaced element
835 					static if (usePrimeCapacity)
836 						immutable hitIndex = xxx;
837 					else
838 						immutable hitIndex = _store.elts[].triangularProbeFromIndex!(pred, assumeNonFullHaystack)(keyToIndex(keyOf(currentElement)));
839 					assert(hitIndex != _store.elts.length, "no free slot");
840 
841 					bts(dones, hitIndex); // _store.elts[hitIndex] will be at it's correct position
842 
843 					if (isOccupiedAtIndex(doneIndex)) {
844 						T nextElement = void;
845 
846 						/+ TODO: functionize: +/
847 						moveEmplace(_store.elts[hitIndex], nextElement); // save non-free slot
848 						static if (is(K == Nullable!(_), _))
849 							keyOf(_store.elts[hitIndex]).nullify(); // `moveEmplace` doesn't init source of type Nullable
850 
851 						moveEmplace(currentElement, _store.elts[hitIndex]);
852 						moveEmplace(nextElement, currentElement);
853 					} else { // if no free slot
854 						moveEmplace(currentElement, _store.elts[hitIndex]);
855 						break; // inner iteration is finished
856 					}
857 				}
858 			}
859 			bts(dones, doneIndex); // _store.elts[doneIndex] is at it's correct position
860 		}
861 
862 		allocator.deallocate(cast(void[])(dones[0 .. wordCountOfBitCount(_store.elts.length)]));
863 	}
864 
865 	/** Grow (with rehash) store in-place making room for `minimumCapacity` number of elements. */
866 	private void growInPlaceWithCapacity()(in size_t minimumCapacity) @trusted /*tlm*/ {
867 		assert(minimumCapacity > _store.elts.length);
868 		static if (usePrimeCapacity)
869 			immutable newCapacity = ceilingPrime(minimumCapacity, _primeIndex);
870 		else
871 			immutable newCapacity = nextPow2(minimumCapacity);
872 		immutable newByteCount = T.sizeof*newCapacity;
873 		const oldStorePtr = _store.elts.ptr;
874 		immutable oldLength = _store.elts.length;
875 		auto rawStore = cast(void[])_store;
876 		if (allocator.reallocate(rawStore, newByteCount)) {
877 			_store = cast(T[])rawStore;
878 			static if (mustAddGCRange!T) {
879 				if (oldStorePtr !is null)
880 					gc_removeRange(oldStorePtr); // `gc_removeRange` fails for null input
881 				gc_addRange(_store.elts.ptr, newByteCount);
882 			}
883 			/+ TODO: make this an array operation `nullifyAll` or `nullifyN` +/
884 			foreach (ref bin; _store.elts[oldLength .. newCapacity])
885 				keyOf(bin).nullify(); // move this `init` to reallocate() above?
886 			rehashInPlace();
887 		} else
888 			assert(0, "couldn't reallocate bin");
889 	}
890 
891 	/** Insert (without growth) `element` at `hitIndex`. */
892 	private InsertionStatus insertWithoutGrowth(SomeElement)(in SomeElement element, /*tlm*/
893 															 out size_t hitIndex) @trusted {
894 		version (LDC) pragma(inline, true);
895 		version (unittest) {
896 			assert(!keyOf(element).isNull);
897 			static if (hasHoleableKey) { assert(!isHoleKeyConstant(adjustKey(keyOf(element)))); }
898 		}
899 		size_t holeIndex = size_t.max; // first hole index to written to if hole found
900 		const hitIndexPrel = indexOfKeyOrVacancyAndFirstHole(keyOf(element), holeIndex);
901 		if (hitIndexPrel == _store.elts.length || // keys miss and holes may have filled all empty slots
902 			keyOf(_store.elts[hitIndexPrel]).isNull) // just key miss but a hole may have been found on the way
903 		{
904 			immutable hasHole = holeIndex != size_t.max; // hole was found along the way
905 			if (hasHole)
906 				hitIndex = holeIndex; // pick hole instead
907 			else
908 				hitIndex = hitIndexPrel; // normal hit
909 			version (unittest) assert(hitIndex != _store.elts.length, "no null or hole slot");
910 			static if (__traits(isPOD, SomeElement))
911 				insertElementAtIndex(*cast(SomeElement*)&element, hitIndex);
912 			else
913 				insertElementAtIndex(move(*cast(SomeElement*)&element), hitIndex);
914 			static if (!hasHoleableKey)
915 				if (hasHole)
916 					untagHoleAtIndex(hitIndex);
917 			_count = _count + 1;
918 			return InsertionStatus.added;
919 		}
920 		else
921 			hitIndex = hitIndexPrel;
922 		static if (hasValue) {
923 			static if (__traits(isStaticArray, V))
924 				// identity comparison of static arrays implicitly coerces them
925 				// to slices, which are compared by reference, so don't use !is here
926 				immutable valueDiffers = (valueOf(element) !=
927 										  valueOf(_store.elts[hitIndexPrel])); // only value changed
928 			else
929 				immutable valueDiffers = (valueOf(element) !is
930 										  valueOf(_store.elts[hitIndexPrel])); // only value changed
931 			if (valueDiffers) { // only value changed
932 				move(valueOf(*cast(SomeElement*)&element),
933 					 valueOf(_store.elts[hitIndexPrel])); // value is defined so overwrite it
934 				return InsertionStatus.modified;
935 			}
936 		}
937 		return InsertionStatus.unmodified;
938 	}
939 
940 	/** Insert (without growth) `element` and return index to bin where insertion happended. */
941 	private size_t insertWithoutGrowthNoStatus(SomeElement)(in SomeElement element) @trusted /*tlm*/ {
942 		version (LDC) pragma(inline, true);
943 		version (unittest) {
944 			assert(!keyOf(element).isNull);
945 			static if (hasHoleableKey) { assert(!isHoleKeyConstant(adjustKey(keyOf(element)))); }
946 		}
947 		size_t hitIndex = 0;
948 		size_t holeIndex = size_t.max; // first hole index to written to if hole found
949 		const hitIndexPrel = indexOfKeyOrVacancyAndFirstHole(adjustKey(keyOf(element)), holeIndex);
950 		if (hitIndexPrel == _store.elts.length || // keys miss and holes may have filled all empty slots
951 			keyOf(_store.elts[hitIndexPrel]).isNull) // just key miss but a hole may have been found on the way
952 		{
953 			immutable hasHole = holeIndex != size_t.max; // hole was found along the way
954 			if (hasHole)
955 				hitIndex = holeIndex; // pick hole instead
956 			else
957 				hitIndex = hitIndexPrel; // normal hit
958 			version (unittest) assert(hitIndex != _store.elts.length, "no null or hole slot");
959 			static if (__traits(isPOD, SomeElement))
960 				insertElementAtIndex(*cast(SomeElement*)&element, hitIndex);
961 			else
962 				insertElementAtIndex(move(*cast(SomeElement*)&element), hitIndex);
963 			static if (!hasHoleableKey)
964 				if (hasHole) { untagHoleAtIndex(hitIndex); }
965 			_count = _count + 1;
966 			return hitIndex;
967 		}
968 		else
969 			hitIndex = hitIndexPrel;
970 		static if (hasValue)
971 			// modify existing value
972 			move(valueOf(*cast(SomeElement*)&element),
973 				 valueOf(_store.elts[hitIndexPrel])); // value is defined so overwrite it
974 		return hitIndex;
975 	}
976 
977 	/** Insert `element`, being either a key-value (map-case) or a just a key (set-case).
978 	 */
979 	private InsertionStatus insertMoveWithoutGrowth()(ref T element) /*tlm*/ {
980 		version (LDC) pragma(inline, true);
981 		size_t hitIndex = 0;
982 		return insertWithoutGrowth(move(element), hitIndex);
983 	}
984 
985 	static if (hasValue) {
986 		/** Insert or replace `value` at `key`. */
987 		InsertionStatus insert()(K key, V value) /*tlm*/ {
988 			pragma(inline, true); // LDC must have this
989 			static if (__traits(isPOD, K)) {
990 				static if (__traits(isPOD, V))
991 					return insert(T(key, value));
992 				else
993 					return insert(T(key, move(value)));
994 			} else {
995 				static if (__traits(isPOD, V))
996 					return insert(T(move(key), value));
997 				else
998 					return insert(T(move(key), move(value)));
999 			}
1000 		}
1001 	}
1002 
1003 	static if (!hasValue) {
1004 		scope const(K)* opBinaryRight(string op, SomeKey)(in SomeKey key) const return @trusted
1005 		if (op == `in` &&
1006 			isScopedKeyType!(typeof(key)))
1007 		in(isValidKey(key)) {
1008 			version (D_Coverage) {} else pragma(inline, true);
1009 			static if (hasHoleableKey) { assert(!isHoleKeyConstant(cast(K)adjustKey(key))); }
1010 			immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted
1011 			immutable match = hitIndex != _store.elts.length && isOccupiedAtIndex(hitIndex);
1012 			if (match)
1013 				return &_store.elts[hitIndex];
1014 			else
1015 				return null;
1016 		}
1017 
1018 		ref typeof(this) opOpAssign(string op, SomeKey)(in SomeKey key) return @trusted
1019 		if (op == `~` &&		// binary assignment operator `~=`
1020 			isScopedKeyType!(typeof(key))) {
1021 			version (LDC) pragma(inline, true);
1022 			reserveExtra(1);
1023 			immutable hitIndex = insertWithoutGrowthNoStatus(key);
1024 			return this;
1025 		}
1026 
1027 		/** Try to retrieve `class`-element of type `Class` constructed with
1028 		 * parameters `params`.
1029 		 *
1030 		 * Typically used to implement (polymorphic) caching of class-types
1031 		 * without the need for GG-allocating a temporary instance of a
1032 		 * `class`-element potentially already stored in `this` set.
1033 		 *
1034 		 * Polymorphic caching can be realized by setting `hasher` to
1035 		 * `hash_functions.hashOfPolymorphic`.
1036 		 */
1037 		scope const(Class) tryGetElementFromCtorParams(Class, Params...)(scope Params params) const return @trusted
1038 		if (is(Class : K)) {
1039 			import core.lifetime : emplace;
1040 			void[__traits(classInstanceSize, Class)] tempNode_ = void;
1041 			scope Class temp = emplace!(Class)(tempNode_, params);
1042 			Class* hit = cast(Class*)(temp in this);
1043 
1044 			static if (__traits(hasMember, Class, "__dtor"))
1045 				temp.__dtor();
1046 
1047 			if (hit) {
1048 				auto typedHit = cast(typeof(return))*hit;
1049 				assert(typedHit, "Expected class " ~ Class.stringof ~ " but got hit was of other type"); /+ TODO: give warning or throw +/
1050 				return typedHit;
1051 			}
1052 			return null;
1053 		}
1054 	}
1055 
1056 	static if (hasValue) {
1057 		scope inout(V)* opBinaryRight(string op, SomeKey)(in SomeKey key) inout return @trusted // `auto ref` here makes things slow
1058 		if (op == `in` &&
1059 			isScopedKeyType!(SomeKey)) {
1060 			version (LDC) pragma(inline, true);
1061 			// pragma(msg, SomeKey, " => ", K);
1062 			immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKey(key)); // cast scoped `key` is @trusted
1063 			immutable match = hitIndex != _store.elts.length && isOccupiedAtIndex(hitIndex);
1064 			if (match)
1065 				return cast(typeof(return))&_store.elts[hitIndex].value;
1066 			else
1067 				return null;
1068 		}
1069 
1070 		/// Indexing.
1071 		scope ref inout(V) opIndex(SomeKey)(in SomeKey key) inout return @trusted // `auto ref` here makes things slow.
1072 		if (isScopedKeyType!(typeof(key))) {
1073 			import core.exception : onRangeError;
1074 			version (LDC) pragma(inline, true);
1075 			immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted
1076 			immutable match = hitIndex != _store.elts.length && isOccupiedAtIndex(hitIndex);
1077 			if (!match)
1078 				onRangeError();
1079 			return _store.elts[hitIndex].value;
1080 		}
1081 
1082 		/** Get value of `key` or `defaultValue` if `key` not present (and
1083 		 * therefore `nothrow`).
1084 		 *
1085 		 * Returns: value reference iff `defaultValue` is an l-value.
1086 		 *
1087 		 * TODO: make `defaultValue` `lazy` when that can be `nothrow`
1088 		 */
1089 		auto ref inout(V) get()(in K key, auto ref inout(V) defaultValue) inout /*tlm*/ {
1090 			version (LDC) pragma(inline, true);
1091 			if (auto valuePtr = key in this)
1092 				return *valuePtr;
1093 			else
1094 				return defaultValue;
1095 		}
1096 
1097 		/** Get reference to `key`-part of stored element at `key`, if present,
1098 		 * otherwise return `defaultKey`.
1099 		 *
1100 		 * Used to implement caching inside the key part of a map.
1101 		 */
1102 		ref const(K) getKeyRef(SomeKey)(in SomeKey key, ref const(K) defaultKey) const return @trusted @nogc
1103 		if (isScopedKeyType!(SomeKey)) {
1104 			version (LDC) pragma(inline, true);
1105 			immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted
1106 			immutable match = hitIndex != _store.elts.length && isOccupiedAtIndex(hitIndex);
1107 			if (match)
1108 				return _store.elts[hitIndex].key;
1109 			return defaultKey;
1110 		}
1111 
1112 		/** Supports the syntax `aa[key] = value;`.
1113 		 */
1114 		ref V opIndexAssign()(V value, K key) /* tlm. TODO: return scope */
1115 		in(isValidKey(key)) {
1116 			version (LDC) pragma(inline, true);
1117 			static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(key)); }
1118 			static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
1119 			reserveExtra(1);
1120 			static if (__traits(isPOD, K)) {
1121 				static if (__traits(isPOD, V))
1122 					immutable hitIndex = insertWithoutGrowthNoStatus(T(key, value));
1123 				else
1124 					immutable hitIndex = insertWithoutGrowthNoStatus(T(key, move(value)));
1125 			} else {
1126 				static if (__traits(isPOD, V))
1127 					immutable hitIndex = insertWithoutGrowthNoStatus(T(move(key), value));
1128 				else
1129 					immutable hitIndex = insertWithoutGrowthNoStatus(T(move(key), move(value)));
1130 			}
1131 			return _store.elts[hitIndex].value;
1132 		}
1133 
1134 		ref V opIndexOpAssign(string op, Rhs)(Rhs rhs, K key) /+ TODO: return scope +/
1135 		// if (true)			   /+ TODO: pre-check that mixin will work +/
1136 		in(isValidKey(key)) {
1137 			// pragma(msg, "opIndexOpAssign: Key:", K, " Value:", V, " Rhs:", Rhs, " op:", op);
1138 			static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(key)); }
1139 			static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
1140 			reserveExtra(1);
1141 			size_t holeIndex = size_t.max; // first hole index to written to if hole found
1142 			immutable hitIndex = indexOfKeyOrVacancyAndFirstHole(key, holeIndex);
1143 			if (hitIndex == _store.elts.length || // keys miss and holes may have filled all empty slots
1144 				keyOf(_store.elts[hitIndex]).isNull) // just key miss but a hole may have been found on the way
1145 			{
1146 				immutable hasHole = holeIndex != size_t.max; // hole was found along the way
1147 				immutable index = (hasHole ?
1148 							   holeIndex : // pick hole instead
1149 							   hitIndex); // normal hit
1150 				version (unittest) assert(index != _store.elts.length, "no null or hole slot");
1151 				static if (__traits(isCopyable, K)) {
1152 					static if (op == "~" ||
1153 							   op == "+" ||
1154 							   op == "*") {
1155 						static if (is(V : Rhs[])) // isDynamicArray of `Rhs`
1156 							insertElementAtIndex(T(key, [rhs]), /+ TODO: if `V(rhs)` is not supported use `V.init` followed by `OP= rhs` +/
1157 												 index);
1158 						else
1159 							// dbg("opIndexOpAssign-new: k:", key, " rhs:", rhs);
1160 							insertElementAtIndex(T(key, V(rhs)), /+ TODO: if `V(rhs)` is not supported use `V.init` followed by `OP= rhs` +/
1161 												 index);
1162 					}
1163 					else
1164 						static assert(0, "Handel op " ~ op);
1165 				}
1166 				else
1167 					static assert(0, "Handle uncopyable key " ~ K.stringof);
1168 					// insertElementAtIndex(move(*cast(SomeElement*)&element), index);
1169 				static if (!hasHoleableKey)
1170 					if (hasHole) { untagHoleAtIndex(index); }
1171 				_count = _count + 1;
1172 				return _store.elts[index].value;
1173 			}
1174 			else { // `key`-hit at index `hitIndex`
1175 				// dbg("opIndexOpAssign-mod: k:", key, " rhs:", rhs);
1176 				mixin(`return _store.elts[hitIndex].value ` ~ op ~ `= rhs;`); // modify existing value
1177 			}
1178 		}
1179 	}
1180 
1181 	static if (!options.growOnly) {
1182 		/** Remove `element`.
1183 		 * Returns: `true` if element was removed, `false` otherwise.
1184 		 */
1185 		bool remove(SomeKey)(in SomeKey key) scope /*tlm*/
1186 		if (isScopedKeyType!(typeof(key))) {
1187 			version (LDC) pragma(inline, true);
1188 			static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
1189 			static if (options.linearSearchMaxSize != 0)
1190 				if (_store.elts.length * T.sizeof <= options.linearSearchMaxSize) {
1191 					foreach (immutable i, const ref element; _store.elts) // linear search is faster for small arrays
1192 						if (keyEqualPredFn(keyOf(element), key)) {
1193 							tagAsLazilyDeletedElementAtIndex(i);
1194 							_count = _count - 1;
1195 							return true;
1196 						}
1197 					return false;
1198 				}
1199 			immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKey(key));
1200 			immutable match = hitIndex != _store.elts.length && isOccupiedAtIndex(hitIndex);
1201 			if (match) {
1202 				tagAsLazilyDeletedElementAtIndex(hitIndex);
1203 				_count = _count - 1;
1204 				return true;
1205 			}
1206 			return false;
1207 		}
1208 
1209 		import nxt.traits_ex : isRefIterable;
1210 		import std.range.primitives : front;
1211 
1212 		/** Remove all elements matching `keys` followed by a rehash.
1213 		 *
1214 		 * Returns: number of elements that were removed.
1215 		 */
1216 		version (none)											/+ TODO: enable +/
1217 		size_t rehashingRemoveN(Keys)(in Keys keys) /*tlm*/
1218 		if (isRefIterable!Keys &&
1219 			is(typeof(Keys.front == K.init))) {
1220 			static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); }
1221 			rehash!("!a.isNull && keys.canFind(a)")(); /+ TODO: make this work +/
1222 			return 0;
1223 		}
1224 	}
1225 
1226 	/// Check if empty.
1227 	@property bool empty() const => _count == 0;
1228 	/// Get length.
1229 	@property size_t length() const => _count;
1230 	/// Get element count.
1231 	alias count = length;
1232 	/// Get bin count (capacity).
1233 	@property size_t binCount() const => _store.elts.length;
1234 
1235 	/** Returns: get total probe count for all elements stored. */
1236 	size_t totalProbeCount()() const /*tlm*/ {
1237 		static if (hasValue)
1238 			auto range = byKeyValue(this);
1239 		else
1240 			auto range = byElement(this);
1241 		typeof(return) totalCount = 0;
1242 		foreach (const ref currentElement; range) {
1243 			static if (__traits(isCopyable, T)) // TODO why does using `passElementByValue` fail as an expression for certain element types?
1244 				/* don't use `auto ref` for copyable `T`'s to prevent
1245 				 * massive performance drop for small elements when compiled
1246 				 * with LDC. TODO: remove when LDC is fixed. */
1247 				alias pred = (const scope element) => (keyEqualPredFn(keyOf(element),
1248 																	  keyOf(currentElement)));
1249 			else
1250 				alias pred = (const scope ref element) => (keyEqualPredFn(keyOf(element),
1251 																		  keyOf(currentElement)));
1252 			static if (usePrimeCapacity)
1253 				immutable probeCount = xxx;
1254 			else
1255 				immutable probeCount = triangularProbeCountFromIndex!(pred)(_store.elts[], keyToIndex(keyOf(currentElement)));
1256 			totalCount += probeCount;
1257 		}
1258 		return totalCount;
1259 	}
1260 
1261 	/** Returns: average probe count for all elements stored. */
1262 	double averageProbeCount()() const /*tlm*/ => (cast(typeof(return))totalProbeCount)/length;
1263 
1264 	/** Unsafe access to raw store.
1265 	 *
1266 	 * Needed by wrapper containers such as `SSOHybridHashSet`.
1267 	 */
1268 	pragma(inline, true)
1269 	inout(T)[] rawStore() inout @system pure nothrow @nogc => _store.elts;
1270 
1271 	static if (hasHoleableKey) {
1272 		static bool isOccupiedBin(in T bin) => (keyOf(bin).isNull && !isHoleKeyConstant(keyOf(bin)));
1273 	}
1274 
1275 private:
1276 	import nxt.allocator_traits : AllocatorState;
1277 	mixin AllocatorState!Allocator; // put first as emsi-containers do
1278 
1279 	struct Store {
1280 		/** Make store with `capacity` number of slots.
1281 		 *
1282 		 * If `initFlag` is true then initialize the elements.
1283 		 */
1284 		static typeof(this) withCapacity(in size_t capacity, in bool initFlag) @trusted pure nothrow @nogc
1285 		{
1286 			static if (usePrimeCapacity) {
1287 				/+ TODO: check that capacity is prime? +/
1288 			} else {
1289 				debug import std.math : isPowerOf2;
1290 				debug assert(capacity.isPowerOf2); // quadratic probing needs power of two capacity (`_store.elts.length`)
1291 			}
1292 			/+ TODO: cannot use makeArray here because it cannot handle uncopyable types +/
1293 			// import std.experimental.allocator : makeArray;
1294 			// auto store = allocator.makeArray!T(capacity, nullKeyElement);
1295 			import nxt.bit_traits : isAllZeroBits;
1296 			immutable eltByteCount = T.sizeof*capacity;
1297 			static if (!hasHoleableKey) {
1298 				immutable holeByteCount = binBlockBytes(capacity);
1299 				immutable totalByteCount = eltByteCount + holeByteCount;
1300 			} else
1301 				immutable totalByteCount = eltByteCount;
1302 			static if (hasAddressLikeKey ||
1303 					   (__traits(isZeroInit, K)  &&
1304 						__traits(hasMember, K, "nullifier")) ||
1305 					   /+ TODO: add check for __traits(isZeroInit, K) and member `K.nullValue` == `K.init` +/
1306 					   (__traits(hasMember, K, `nullValue`) && // if key has a null value
1307 						__traits(compiles, { enum _ = isAllZeroBits!(K, K.nullValue); }) && // prevent strange error given when `K` is `knet.data.Data`
1308 						isAllZeroBits!(K, K.nullValue))) // check that it's zero bits only
1309 			{
1310 				/+ TODO: use std.experimental.allocator.makeArray instead of this which handles clever checking for isZeroInit +/
1311 				import nxt.container.traits : makeInitZeroArray;
1312 				static if (__traits(hasMember, typeof(allocator), "allocateZeroed") &&
1313 						  is(typeof(allocator.allocateZeroed(totalByteCount))))
1314 					auto rawStore = allocator.allocateZeroed(totalByteCount);
1315 				else {
1316 					auto rawStore = allocator.allocate(totalByteCount);
1317 					(cast(ubyte[])rawStore)[] = 0;	/+ TODO: is this the most efficient way? +/
1318 				}
1319 				if (rawStore.ptr is null &&
1320 					capacity >= 1)
1321 					onOutOfMemoryError();
1322 				static if (!hasHoleableKey) {
1323 					auto holes = rawStore[eltByteCount .. totalByteCount]; /+ TODO: currently unused +/
1324 				}
1325 				auto store = typeof(this)(cast(T[])rawStore[0 .. eltByteCount]);
1326 			} else { // when default null key is not represented by zeros
1327 				// pragma(msg, "emplace:", "K:", K, " V:", V);
1328 				auto rawStore = allocator.allocate(totalByteCount);
1329 				if (rawStore.ptr is null &&
1330 					totalByteCount >= 1)
1331 					onOutOfMemoryError();
1332 				auto store = typeof(this)(cast(T[])rawStore[0 .. eltByteCount]);
1333 				static if (!hasHoleableKey) {
1334 					size_t[] holes = (cast(size_t*)(store.elts.ptr + store.elts.length))[0 .. holesWordCount(capacity)];
1335 					holes[] = 0; /+ TODO: is this the most efficient way? +/
1336 				}
1337 				if (initFlag) {
1338 					foreach (ref bin; store.elts) {
1339 						import core.lifetime : emplace;
1340 						enum hasNullValueKey = __traits(hasMember, K, `nullValue`);
1341 						static if (hasNullValueKey &&
1342 							   !is(typeof(emplace(&keyOf(bin), K.nullValue)))) // __traits(compiles) fails here when building knet
1343 							pragma(msg, __FILE__, ":", __LINE__, ":warning: emplace fails for null-Value key type ", K);
1344 						// initialize key
1345 						static if (hasNullValueKey &&
1346 								   is(typeof(emplace(&keyOf(bin), K.nullValue))))
1347 							emplace(&keyOf(bin), K.nullValue); // initialize in-place with explicit `K.nullValue`
1348 						else {
1349 							emplace(&keyOf(bin)); // initialize in-place with default value
1350 							keyOf(bin).nullify(); // moveEmplace doesn't init source of type `Nullable`
1351 						}
1352 						// initialize value
1353 						static if (hasValue) {
1354 							static if (hasElaborateDestructor!V)
1355 								emplace(&valueOf(bin)); // initialize in-place
1356 							else static if (mustAddGCRange!V)
1357 								valueOf(bin) = V.init;
1358 							else {}	// ok for this case to have uninitialized value part
1359 						}
1360 					}
1361 				}
1362 			}
1363 			static if (mustAddGCRange!T)
1364 				gc_addRange(store.elts.ptr, eltByteCount);
1365 			return store;
1366 		}
1367 
1368 		static if (hasFunctionAttributes!(allocator.allocate, "@nogc")) {
1369 			import nxt.gc_traits : NoGc;
1370 			@NoGc T[] elts;		// one element per bin
1371 		} else
1372 			T[] elts;			  // one element per bin
1373 		static if (!hasHoleableKey)
1374 			inout(size_t)* holesPtr() inout @property @trusted pure nothrow @nogc => cast(size_t*)(elts.ptr + elts.length);
1375 	}
1376 	Store _store;
1377 
1378 	static if (usePrimeCapacity)
1379 		PrimeIndex _primeIndex = PrimeIndex.init;
1380 
1381 	size_t _count;		// total number of (non-null) elements stored in `_store`
1382 
1383 	static if (isBorrowChecked) {
1384 		debug { // use Rust-style borrow checking at run-time
1385 			size_t _borrowCount;
1386 
1387 			/// Number of bits needed to store number of read borrows.
1388 			enum borrowCountBits = 8*isBorrowChecked.sizeof;
1389 
1390 			/// Maximum value possible for `_borrowCount`.
1391 			enum borrowCountMax = 2^^borrowCountBits - 1;
1392 
1393 			version (none) {
1394 				/// Number of bits needed to store number of read borrows.
1395 				enum borrowCountBits = 24;
1396 
1397 				/// Maximum value possible for `_borrowCount`.
1398 				enum borrowCountMax = 2^^borrowCountBits - 1;
1399 
1400 				import std.bitmanip : bitfields;
1401 				mixin(bitfields!(size_t, "_count", 8*size_t.sizeof - borrowCountBits,
1402 								 uint, "_borrowCount", borrowCountBits));
1403 			}
1404 
1405 			pragma(inline, true):
1406 			pure nothrow @safe @nogc:
1407 
1408 			@property {
1409 				/// Returns: `true` iff `this` is borrowed (either read or write).
1410 				bool isBorrowed() const => _borrowCount >= 1;
1411 				/// Returns: number of borrowers of `this` (both read and write).
1412 				auto borrowCount() const => _borrowCount;
1413 			}
1414 
1415 			/// Increase borrow count.
1416 			void incBorrowCount()
1417 			in(_borrowCount + 1 != borrowCountMax) {
1418 				_borrowCount = _borrowCount + 1;
1419 			}
1420 
1421 			/// Decrease borrow count.
1422 			void decBorrowCount()
1423 			in(_borrowCount != 0) {
1424 				_borrowCount = _borrowCount - 1;
1425 			}
1426 		}
1427 	}
1428 
1429 	/** Returns: bin index of `key`. */
1430 	private size_t keyToIndex(SomeKey)(in SomeKey key) const @trusted {
1431 		version (LDC) pragma(inline, true); /+ TODO: inline always +/
1432 
1433 		/** Returns: current index mask from bin count `_store.elts.length`.
1434 		 * TODO: Inline this and check for speed-up.
1435 		 */
1436 		static size_t powerOf2Mask(in size_t length) pure nothrow @safe @nogc {
1437 			version (unittest) {
1438 				/+ TODO: move to in contract: +/
1439 				debug import std.math : isPowerOf2;
1440 				debug assert(length.isPowerOf2); // quadratic probing needs power of two capacity (`_store.elts.length`)
1441 			} else {
1442 				version (D_Coverage) {} else pragma(inline, true);
1443 			}
1444 			return length - 1;
1445 		}
1446 
1447 		static if (is(typeof(hasher(key)) == hash_t)) // for instance when hasher being `hashOf`
1448 			immutable hash = hasher(key);
1449 		else static if (is(hasher == struct) || // such as `FNV`
1450 						is(hasher == class)) {
1451 			import nxt.digestion : hashOf2;
1452 			immutable hash = hashOf2!(hasher)(key);
1453 		} else
1454 			static assert(false, "Unsupported hasher of type " ~ typeof(hasher).stringof);
1455 		static if (usePrimeCapacity)
1456 			return moduloPrimeIndex(hash, _primeIndex);
1457 		else
1458 			return hash & powerOf2Mask(_store.elts.length);
1459 	}
1460 
1461 	/** Find index to `key` if it exists or to first empty slot found, skipping
1462 	 * (ignoring) lazily deleted slots.
1463 	 */
1464 	private size_t indexOfKeyOrVacancySkippingHoles(in K key) const @trusted scope { // `auto ref` here makes things slow
1465 	/+ TODO: if (...) +/
1466 		version (LDC) pragma(inline, true);
1467 		version (unittest) {
1468 			assert(!key.isNull);
1469 			static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); }
1470 		}
1471 		static if (options.linearSearchMaxSize != 0)
1472 			if (_store.elts.length * T.sizeof <= options.linearSearchMaxSize) {
1473 				foreach (immutable i, const ref element; _store.elts) // linear search is faster for small arrays
1474 					if ((keyOf(element).isNull ||
1475 						 keyEqualPredFn(keyOf(element), key)))
1476 						return i;
1477 				return _store.elts.length;
1478 			}
1479 		static if (hasHoleableKey)
1480 			alias pred = (in element) => (keyOf(element).isNull ||
1481 										  keyEqualPredFn(keyOf(element), key));
1482 		else
1483 			alias pred = (in index,
1484 						  in element) => (!hasHoleAtPtrIndex(_store.holesPtr, index) &&
1485 										  (keyOf(element).isNull ||
1486 										   keyEqualPredFn(keyOf(element), key)));
1487 		static if (usePrimeCapacity)
1488 			return xxx;
1489 		else
1490 			return _store.elts[].triangularProbeFromIndex!(pred, assumeNonFullHaystack)(keyToIndex(key));
1491 	}
1492 
1493 	private size_t indexOfKeyOrVacancyAndFirstHole(in K key, // `auto ref` here makes things slow
1494 												   ref size_t holeIndex) const @trusted scope
1495 	{
1496 		version (LDC) pragma(inline, true);
1497 		version (unittest) {
1498 			assert(!key.isNull);
1499 			static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); }
1500 		}
1501 		static if (options.linearSearchMaxSize != 0)
1502 			if (_store.elts.length * T.sizeof <= options.linearSearchMaxSize) {
1503 				foreach (immutable i, const ref element; _store.elts) // linear search is faster for small arrays
1504 					if ((keyOf(element).isNull ||
1505 						 keyEqualPredFn(keyOf(element), key)))
1506 						return i;
1507 				return _store.elts.length;
1508 			}
1509 		static if (hasHoleableKey) {
1510 			alias hitPred = (in element) => (keyOf(element).isNull ||
1511 											 keyEqualPredFn(keyOf(element), key));
1512 			alias holePred = (in element) => (isHoleKeyConstant(keyOf(element)));
1513 		} else {
1514 			alias hitPred = (in index,
1515 							 in element) => (!hasHoleAtPtrIndex(_store.holesPtr, index) &&
1516 											 (keyOf(element).isNull ||
1517 											  keyEqualPredFn(keyOf(element), key)));
1518 			alias holePred = (in index, /+ TODO: use only index +/
1519 							  in element) => (hasHoleAtPtrIndex(_store.holesPtr, index));
1520 		}
1521 		static if (usePrimeCapacity)
1522 			return xxx;
1523 		else
1524 			return _store.elts[].triangularProbeFromIndexIncludingHoles!(hitPred, holePred, assumeNonFullHaystack)(keyToIndex(key), holeIndex);
1525 	}
1526 
1527 	/// Returns: `true` iff `index` indexes a non-null element, `false` otherwise.
1528 	private bool isOccupiedAtIndex(in size_t index) const {
1529 		version (LDC) pragma(inline, true);
1530 		version (unittest) assert(index < _store.elts.length);
1531 		if (keyOf(_store.elts[index]).isNull) { return false; }
1532 		static if (hasHoleableKey)
1533 			return !isHoleKeyConstant(keyOf(_store.elts[index]));
1534 		else
1535 			return !hasHoleAtPtrIndex(_store.holesPtr, index);
1536 	}
1537 }
1538 
1539 /** Duplicate `src` into uninitialized `dst` ignoring prior destruction of `dst`.
1540  *
1541  * TODO: Move to a more generic place either in phobos-next or Phobos.
1542  */
1543 static private void duplicateEmplace(T)(in T src,
1544 										scope ref T dst) @system {
1545 	version (D_Coverage) {} else pragma(inline, true);
1546 	import core.internal.traits : hasElaborateCopyConstructor;
1547 	import std.traits : isBasicType;
1548 	static if (!hasElaborateCopyConstructor!T) {
1549 		import std.typecons : Nullable;
1550 		static if (is(T == class) ||
1551 				   is(T == string))
1552 			dst = cast(T)src;
1553 		else static if (isBasicType!T || is(T == Nullable!(_), _)) // `Nullable` types cannot be emplaced
1554 			dst = src;
1555 		else { /+ TODO: can this case occur? +/
1556 			import core.internal.traits : Unqual;
1557 			import core.lifetime : emplace;
1558 			emplace(&dst, cast(Unqual!T)src);
1559 		}
1560 	}
1561 	else static if (__traits(hasMember, T, "dup")) {
1562 		import core.lifetime : emplace;
1563 		/+ TODO: when `emplace` can handle src being an r-value of uncopyable types replace with: `emplace(&dst, src.dup);` +/
1564 		emplace(&dst);
1565 		dst = src.dup;
1566 	}
1567 	else
1568 		debug static assert(0, "cannot duplicate a " ~ T.stringof);
1569 }
1570 
1571 /** L-value element reference (and in turn range iterator).
1572  */
1573 static private struct LvalueElementRef(SomeMap) {
1574 	import std.traits : isMutable;
1575 	debug static assert(isMutable!SomeMap, "SomeMap type must be mutable");
1576 
1577 	private SomeMap* _table;	  // scoped access
1578 	private size_t _binIndex;   // index to bin inside `table`
1579 	private size_t _hitCounter; // counter over number of elements popped (needed for length)
1580 
1581 	this(SomeMap* table) @trusted {
1582 		version (D_Coverage) {} else pragma(inline, true);
1583 		this._table = table;
1584 		static if (SomeMap.isBorrowChecked)
1585 			debug { _table.incBorrowCount(); }
1586 	}
1587 
1588 	~this() nothrow @nogc @trusted {
1589 		version (D_Coverage) {} else pragma(inline, true);
1590 		static if (SomeMap.isBorrowChecked)
1591 			debug { _table.decBorrowCount(); }
1592 	}
1593 
1594 	this(this) @trusted {
1595 		version (D_Coverage) {} else pragma(inline, true);
1596 		static if (SomeMap.isBorrowChecked)
1597 			debug {
1598 				assert(_table._borrowCount != 0);
1599 				_table.incBorrowCount();
1600 			}
1601 	}
1602 
1603 	/// Check if empty.
1604 	bool empty() const @property pure nothrow @safe @nogc {
1605 		version (D_Coverage) {} else pragma(inline, true);
1606 		return _binIndex == _table.binCount;
1607 	}
1608 
1609 	/// Get number of element left to pop.
1610 	@property size_t length() const pure nothrow @safe @nogc {
1611 		version (D_Coverage) {} else pragma(inline, true);
1612 		return _table.length - _hitCounter;
1613 	}
1614 
1615 	@property typeof(this) save() { // ForwardRange
1616 		version (D_Coverage) {} else pragma(inline, true);
1617 		return this;
1618 	}
1619 
1620 	void popFront() in(!empty) {
1621 		version (LDC) pragma(inline, true);
1622 		_binIndex += 1;
1623 		findNextNonEmptyBin();
1624 		_hitCounter += 1;
1625 	}
1626 
1627 	private void findNextNonEmptyBin() {
1628 		version (D_Coverage) {} else pragma(inline, true);
1629 		while (_binIndex != (*_table).binCount &&
1630 			   !(*_table).isOccupiedAtIndex(_binIndex))
1631 			_binIndex += 1;
1632 	}
1633 }
1634 
1635 /** R-value element reference (and in turn range iterator).
1636  *
1637  * Does need to do borrow-checking.
1638  */
1639 static private struct RvalueElementRef(SomeMap) {
1640 	debug import std.traits : isMutable;
1641 	debug static assert(isMutable!SomeMap, "SomeMap type must be mutable");
1642 
1643 	SomeMap _table;				// owned table
1644 	size_t _binIndex;			// index to bin inside table
1645 	size_t _hitCounter;	// counter over number of elements popped
1646 
1647 	/// Check if empty.
1648 	bool empty() const @property pure nothrow @safe @nogc {
1649 		version (D_Coverage) {} else pragma(inline, true);
1650 		return _binIndex == _table.binCount;
1651 	}
1652 
1653 	/// Get number of element left to pop.
1654 	@property size_t length() const pure nothrow @safe @nogc {
1655 		version (D_Coverage) {} else pragma(inline, true);
1656 		return _table.length - _hitCounter;
1657 	}
1658 
1659 	void popFront() in(!empty) {
1660 		version (LDC) pragma(inline, true);
1661 		_binIndex += 1;
1662 		findNextNonEmptyBin();
1663 		_hitCounter += 1;
1664 	}
1665 
1666 	private void findNextNonEmptyBin() {
1667 		version (D_Coverage) {} else pragma(inline, true);
1668 		while (_binIndex != _table.binCount &&
1669 			   !_table.isOccupiedAtIndex(_binIndex))
1670 			_binIndex += 1;
1671 	}
1672 }
1673 
1674 /** Hash set with in-place open-addressing, storing keys (elements) of type `K`.
1675  *
1676  * Reuse `HybridHashMap` with its V-type set to `void`.
1677  *
1678  * See_Also: `HybridHashMap`.
1679  */
1680 alias HybridHashSet(K,
1681 					alias hasher = hashOf,
1682 					string keyEqualPred = defaultKeyEqualPredOf!K,
1683 					Allocator = Mallocator,
1684 					Options options = Options.init) =
1685 	HybridHashMap!(K, void, hasher, keyEqualPred, Allocator, options);
1686 
1687 import std.functional : unaryFun;
1688 
1689 /** Remove all elements in `x` matching `pred`.
1690  *
1691  * TODO: make this generic for all iterable containers and move to
1692  * container/common.d.
1693  */
1694 size_t removeAllMatching(alias pred, SomeMap)(auto ref SomeMap x) @trusted
1695 if (is(SomeMap == HybridHashMap!(_), _...) && /+ TODO: generalize to `isSetOrMap` +/
1696 	is(typeof((unaryFun!pred)))) {
1697 	import nxt.nullable_traits : nullify;
1698 	size_t removalCount = 0;
1699 	foreach (immutable i, ref bin; x._store.elts) {
1700 		/+ TODO: +/
1701 		// move to SomeMap.removeRef(bin) // uses: `offset = &bin - _store.elts.ptr`
1702 		// move to SomeMap.inplaceRemove(bin) // uses: `offset = &bin - _store.elts.ptr`
1703 		// or   to SomeMap.removeAtIndex(i)
1704 		if (x.isOccupiedAtIndex(i) &&
1705 			unaryFun!pred(bin)) {
1706 			x.tagAsLazilyDeletedElementAtIndex(i);
1707 			removalCount += 1;
1708 		}
1709 	}
1710 	x._count = x._count - removalCount;
1711 	return removalCount;		/+ TODO: remove this return value +/
1712 }
1713 
1714 /** Returns: `x` eagerly filtered on `pred`.
1715  *
1716  * TODO: move to container/common.d with more generic template restrictions
1717  */
1718 SomeMap filtered(alias pred, SomeMap)(SomeMap x)
1719 if (is(SomeMap == HybridHashMap!(_), _...)) { /+ TODO: generalize to `isSetOrMap` +/
1720 	import core.lifetime : move;
1721 	import std.functional : not;
1722 	x.removeAllMatching!(not!pred); // `x` is a singleton (r-value) so safe to mutate
1723 	return move(x);			 // functional
1724 }
1725 
1726 /** Returns: `x` eagerly intersected with `y`.
1727  *
1728  * TODO: move to container/common.d.
1729  * TODO: Check that `ElementType`'s of `C1` and `C2` match. Look at std.algorithm.intersection for hints.
1730  */
1731 auto intersectedWith(C1, C2)(C1 x, auto ref C2 y)
1732 if (is(C1 == HybridHashMap!(_1), _1...) && /+ TODO: generalize to `isSetOrMap` +/
1733 	is(C2 == HybridHashMap!(_2), _2...))   /+ TODO: generalize to `isSetOrMap` +/
1734 {
1735 	import core.lifetime : move;
1736 	static if (__traits(isRef, y)) // y is l-value
1737 		// @("complexity", "O(x.length)")
1738 		return move(x).filtered!(_ => y.contains(_)); // only x can be reused
1739 	else {
1740 		/* both are r-values so reuse the shortest */
1741 		// @("complexity", "O(min(x.length), min(y.length))")
1742 		if (x.length < y.length)
1743 			return move(x).filtered!(_ => y.contains(_)); // functional
1744 		else
1745 			return move(y).filtered!(_ => x.contains(_)); // functional
1746 	}
1747 }
1748 
1749 /// exercise opEquals
1750 pure nothrow @safe @nogc unittest {
1751 	import std.typecons : Nullable;
1752 	import nxt.digest.fnv : FNV;
1753 
1754 	alias K = Nullable!(ulong, ulong.max);
1755 	alias X = HybridHashSet!(K, FNV!(64, true));
1756 
1757 	const n = 100;
1758 
1759 	X a;
1760 	foreach (const i_; 0 .. n) {
1761 		const i = 1113*i_;		   // insert in order
1762 		assert(!a.contains(K(i)));
1763 		assert(!a.containsUsingLinearSearch(K(i)));
1764 		assert(a.insertAndReturnElement(K(i)) == K(i));
1765 		assert(a.contains(K(i)));
1766 		assert(a.containsUsingLinearSearch(K(i)));
1767 	}
1768 
1769 	X b;
1770 	foreach (const i_; 0 .. n) {
1771 		const i = 1113*(n - 1 - i_);   // insert in reverse
1772 		assert(!b.contains(K(i)));
1773 		assert(!b.containsUsingLinearSearch(K(i)));
1774 		assert(b.insertAndReturnElement(K(i)) == K(i));
1775 		assert(b.contains(K(i)));
1776 		assert(b.containsUsingLinearSearch(K(i)));
1777 	}
1778 
1779 	assert(a == b);
1780 
1781 	// bin storage must be deterministic
1782 	() @trusted { assert(a._store != b._store); }();
1783 }
1784 
1785 pure nothrow @safe @nogc unittest {
1786 	import nxt.digest.fnv : FNV;
1787 
1788 	enum Pot { noun, verb }
1789 	struct ExprPot {
1790 		string expr;
1791 		alias nullifier = expr;
1792 		Pot pot;
1793 		static immutable nullValue = typeof(this).init;
1794 	}
1795 
1796 	alias X = HybridHashSet!(ExprPot, FNV!(64, true));
1797 
1798 	X x;
1799 
1800 	const aa = "aa";
1801 
1802 	// two keys with same contents but different place in memory
1803 	const key1 = ExprPot(aa[0 .. 1], Pot.noun);
1804 	const key2 = ExprPot(aa[1 .. 2], Pot.noun);
1805 
1806 	assert(key1 == key2);
1807 	assert(key1 !is key2);
1808 
1809 	assert(!x.contains(key1));
1810 	assert(!x.contains(key2));
1811 	x.insert(key1);
1812 	assert(x.contains(key1));
1813 	assert(x.containsUsingLinearSearch(key1));
1814 	assert(x.contains(key2));
1815 	/* assert(x.containsUsingLinearSearch(key2)); */
1816 	assert(key1 in x);
1817 	assert(key2 in x);
1818 }
1819 
1820 /// `string` as key
1821 pure nothrow @safe @nogc unittest {
1822 	import nxt.container.traits : mustAddGCRange;
1823 	import nxt.digest.fnv : FNV;
1824 
1825 	alias X = HybridHashSet!(string, FNV!(64, true));
1826 	debug static assert(!mustAddGCRange!X);
1827 	debug static assert(X.sizeof == 24); // dynamic arrays also `hasAddressLikeKey`
1828 
1829 	auto x = X();
1830 
1831 	auto testEscapeShouldFail()() @safe pure {
1832 		X x;
1833 		x.insert("a");
1834 		return x.byElement;
1835 	}
1836 
1837 	auto testEscapeShouldFailFront()() @safe pure {
1838 		X x;
1839 		x.insert("a");
1840 		return x.byElement.front;
1841 	}
1842 
1843 	assert(&"a"[0] is &"a"[0]); // string literals are store in common place
1844 
1845 	const aa = "aa";
1846 
1847 	// string slices are equal when elements are equal regardless of position
1848 	// (.ptr) in memory
1849 	assert(x.insertAndReturnElement(aa[0 .. 1]) !is "a");
1850 	x.insert(aa[0 .. 1]);
1851 	assert(x.insertAndReturnElement(aa[0 .. 1]) is aa[0 .. 1]);
1852 	assert(x.contains(aa[1 .. 2]));
1853 	assert(x.containsUsingLinearSearch(aa[1 .. 2]));
1854 
1855 	const(char)[] aa_ = "aa";
1856 	assert(x.contains(aa_[1 .. 2]));
1857 	assert(x.containsUsingLinearSearch(aa_[1 .. 2]));
1858 	assert(aa_[1 .. 2] in x);
1859 
1860 	char[2] aa__; aa__ = "aa";
1861 	assert(x.contains(aa__[1 .. 2]));
1862 	assert(x.containsUsingLinearSearch(aa__[1 .. 2]));
1863 	assert(aa__[1 .. 2] in x);
1864 
1865 	const bb = "bb";
1866 
1867 	assert(x.insertAndReturnElement(bb[0 .. 1]) is bb[0 .. 1]); // returns newly added ref
1868 	assert(x.insertAndReturnElement(bb[0 .. 1]) !is "b");	   // return other ref not equal new literal
1869 	x.insert(bb[0 .. 1]);
1870 	assert(x.contains(bb[1 .. 2]));
1871 	assert(x.containsUsingLinearSearch(bb[1 .. 2]));
1872 
1873 	x.remove(aa[0 .. 1]);
1874 	assert(!x.contains(aa[1 .. 2]));
1875 	assert(!x.containsUsingLinearSearch(aa[1 .. 2]));
1876 	assert(x.contains(bb[1 .. 2]));
1877 	assert(x.containsUsingLinearSearch(bb[1 .. 2]));
1878 
1879 	x.remove(bb[0 .. 1]);
1880 	assert(!x.contains(bb[1 .. 2]));
1881 	assert(!x.containsUsingLinearSearch(bb[1 .. 2]));
1882 
1883 	x.insert("a");
1884 	x.insert("b");
1885 	assert(x.contains("a"));
1886 	assert(x.containsUsingLinearSearch("a"));
1887 	assert(x.contains("b"));
1888 	assert(x.containsUsingLinearSearch("b"));
1889 
1890 	debug static assert(!__traits(compiles, { testEscapeShouldFail(); } ));
1891 	/+ TODO: this should fail: +/
1892 	/+ TODO: debug static assert(!__traits(compiles, { testEscapeShouldFailFront(); } )); +/
1893 }
1894 
1895 /// `string` as key
1896 pure nothrow @safe unittest {
1897 	import nxt.digest.fnv : FNV;
1898 	alias X = HybridHashSet!(string, FNV!(64, true));
1899 	auto x = X();
1900 
1901 	char[2] cc = "cc";		  // mutable chars
1902 	assert(x.insertAndReturnElement(cc[]) !is cc[]); // will allocate new slice
1903 
1904 	const cc_ = "cc";		   // immutable chars
1905 	assert(x.insertAndReturnElement(cc_[]) !is cc[]); // will not allocate
1906 }
1907 
1908 /// array container as value type
1909 pure nothrow @safe @nogc unittest {
1910 	import std.meta : AliasSeq;
1911 	import std.typecons : Nullable;
1912 	import nxt.container.traits : mustAddGCRange;
1913 	import nxt.digest.fnv : FNV;
1914 	import nxt.array_help : s;
1915 
1916 	alias K = Nullable!(uint, uint.max);
1917 
1918 	alias VE = Nullable!(uint, uint.max);
1919 	alias V = HybridHashSet!(VE, FNV!(64, true));
1920 
1921 	debug static assert(!mustAddGCRange!V);
1922 
1923 	foreach (X; AliasSeq!(HybridHashMap!(K, V, FNV!(64, true)))) {
1924 		const VE n = 600;
1925 
1926 		auto x = X();
1927 
1928 		{					   // scoped range
1929 			auto xkeys = x.byKey;
1930 			assert(xkeys.length == 0);
1931 			foreach (ref key; xkeys) {
1932 				debug static assert(is(typeof(key) == const(K)));
1933 				assert(0);
1934 			}
1935 			foreach (ref key; X().byKey) {
1936 				debug static assert(is(typeof(key) == const(K)));
1937 				assert(0);
1938 			}
1939 		}
1940 
1941 		foreach (immutable i; 0 .. n) {
1942 			assert(x.length == i);
1943 
1944 			auto key = K(i);
1945 			auto value = V.withElements([VE(i)].s);
1946 
1947 			x[key] = value.dup;
1948 			assert(x.length == i + 1);
1949 			assert(x.contains(key));
1950 			/+ TODO: assert(x.containsUsingLinearSearch(key)); +/
1951 			{
1952 				auto valuePtr = key in x;
1953 				assert(valuePtr);
1954 				assert(*valuePtr == value);
1955 			}
1956 
1957 			x.remove(key);
1958 			assert(x.length == i);
1959 			assert(!x.contains(key));
1960 			assert(key !in x);
1961 
1962 			x[key] = value.dup;
1963 			assert(x.length == i + 1);
1964 			assert(x.contains(key));
1965 			{
1966 				auto valuePtr = key in x;
1967 				assert(valuePtr && *valuePtr == value);
1968 			}
1969 		}
1970 
1971 		assert(x is x);
1972 
1973 		x = x.dup;
1974 
1975 		auto y = x.dup;
1976 		assert(x !is y);
1977 		assert(x.length == y.length);
1978 
1979 		assert(y == x);
1980 		assert(x == y);
1981 
1982 		foreach (ref key; x.byKey) {
1983 			assert(x.contains(key));
1984 		}
1985 
1986 		foreach (ref keyValue; x.byKeyValue) {
1987 			assert(x.contains(keyValue.key));
1988 			auto keyValuePtr = keyValue.key in x;
1989 			assert(keyValuePtr &&
1990 				   *keyValuePtr == keyValue.value);
1991 		}
1992 
1993 		foreach (immutable i; 0 .. n) {
1994 			assert(x.length == n - i);
1995 
1996 			auto key = K(i);
1997 			auto value = V.withElements([VE(i)].s);
1998 
1999 			assert(x.contains(key));
2000 			{
2001 				auto valuePtr = key in x;
2002 				assert(valuePtr && *valuePtr == value);
2003 			}
2004 
2005 			x.remove(key);
2006 			assert(!x.contains(key));
2007 			assert(key !in x);
2008 		}
2009 
2010 		auto z = y.dup;
2011 		assert(y == z);
2012 
2013 		/* remove all elements in `y` using `removeAllMatching` and all elements
2014 		 * in `z` using `removeAllMatching` */
2015 		foreach (immutable i; 0 .. n) {
2016 			assert(y.length == n - i);
2017 			assert(z.length == n - i);
2018 
2019 			auto key = K(i);
2020 			auto value = V.withElements([VE(i)].s);
2021 
2022 			assert(y.contains(key));
2023 			{
2024 				auto valuePtr = key in y;
2025 				assert(valuePtr && *valuePtr == value);
2026 			}
2027 			assert(z.contains(key));
2028 			{
2029 				auto valuePtr = key in z;
2030 				assert(valuePtr && *valuePtr == value);
2031 			}
2032 
2033 			y.remove(key);
2034 			assert(z.removeAllMatching!((in element) => element.key is key) == 1);
2035 			assert(y == z);
2036 
2037 			assert(!y.contains(key));
2038 			assert(!z.contains(key));
2039 
2040 			assert(key !in y);
2041 			assert(key !in z);
2042 		}
2043 	}
2044 }
2045 
2046 /// r-value and l-value intersection
2047 pure nothrow @safe @nogc unittest {
2048 	import core.lifetime : move;
2049 	import std.typecons : Nullable;
2050 	import nxt.digest.fnv : FNV;
2051 	import nxt.array_help : s;
2052 
2053 	alias K = Nullable!(uint, uint.max);
2054 	alias X = HybridHashSet!(K, FNV!(64, true));
2055 
2056 	auto x = X();
2057 
2058 	{						   // scoped range
2059 		foreach (ref _; x.byElement) { assert(0); }
2060 	}
2061 
2062 	auto x0 = X.init;
2063 	assert(x0.length == 0);
2064 	assert(x0._store.elts.length == 0);
2065 	assert(!x0.contains(K(1)));
2066 
2067 	auto x1 = X.withElements([K(12)].s);
2068 	assert(x1.length == 1);
2069 	assert(x1.contains(K(12)));
2070 
2071 	auto x2 = X.withElements([K(10), K(12)].s);
2072 	assert(x2.length == 2);
2073 	assert(x2.contains(K(10)));
2074 	assert(x2.contains(K(12)));
2075 
2076 	auto x3 = X.withElements([K(12), K(13), K(14)].s);
2077 	assert(x3.length == 3);
2078 	assert(x3.contains(K(12)));
2079 	assert(x3.contains(K(13)));
2080 	assert(x3.contains(K(14)));
2081 
2082 	auto z = X.withElements([K(10), K(12), K(13), K(15)].s);
2083 	assert(z.length == 4);
2084 	assert(z.contains(K(10)));
2085 	assert(z.contains(K(12)));
2086 	assert(z.contains(K(13)));
2087 	assert(z.contains(K(15)));
2088 
2089 	auto y = move(z).intersectedWith(x2);
2090 	assert(y.length == 2);
2091 	assert(y.contains(K(10)));
2092 	assert(y.contains(K(12)));
2093 	assert(y.containsUsingLinearSearch(K(10)));
2094 	assert(y.containsUsingLinearSearch(K(12)));
2095 }
2096 
2097 /// r-value and r-value intersection
2098 pure nothrow @safe @nogc unittest {
2099 	import std.typecons : Nullable;
2100 	import nxt.digest.fnv : FNV;
2101 	import nxt.array_help : s;
2102 
2103 	alias K = Nullable!(uint, uint.max);
2104 	alias X = HybridHashSet!(K, FNV!(64, true));
2105 
2106 	auto y = X.withElements([K(10), K(12), K(13), K(15)].s).intersectedWith(X.withElements([K(12), K(13)].s));
2107 	assert(y.length == 2);
2108 	assert(y.contains(K(12)));
2109 	assert(y.contains(K(13)));
2110 	assert(y.containsUsingLinearSearch(K(12)));
2111 	assert(y.containsUsingLinearSearch(K(13)));
2112 }
2113 
2114 /** Returns: `x` eagerly intersected with `y`.
2115 	TODO: move to container/common.d.
2116  */
2117 auto intersectWith(C1, C2)(ref C1 x, auto ref const(C2) y)
2118 if (is(C1 == HybridHashMap!(_1), _1) &&
2119 	is(C2 == HybridHashMap!(_2), _2)) {
2120 	return x.removeAllMatching!(_ => !y.contains(_));
2121 }
2122 
2123 /// r-value and l-value intersection
2124 pure nothrow @safe @nogc unittest {
2125 	import std.typecons : Nullable;
2126 	import nxt.digest.fnv : FNV;
2127 	import nxt.array_help : s;
2128 
2129 	alias K = Nullable!(uint, uint.max);
2130 	alias X = HybridHashSet!(K, FNV!(64, true));
2131 
2132 	auto x = X.withElements([K(12), K(13)].s);
2133 	auto y = X.withElements([K(10), K(12), K(13), K(15)].s);
2134 	y.intersectWith(x);
2135 	assert(y.length == 2);
2136 	assert(y.contains(K(12)));
2137 	assert(y.containsUsingLinearSearch(K(12)));
2138 	assert(y.contains(K(13)));
2139 	assert(y.containsUsingLinearSearch(K(13)));
2140 }
2141 
2142 /// Range over elements of l-value instance of this.
2143 static struct ByLvalueElement(SomeMap) // public for now because this is needed in `knet.zing.Zing.EdgesOfRels`
2144 {
2145 	import nxt.container.traits : isAddress;
2146 pragma(inline, true):
2147 	/+ TODO: functionize +/
2148 	static if (isAddress!(SomeMap.ElementType)) // for reference types
2149 	{
2150 		/// Get reference to front element.
2151 		@property scope SomeMap.ElementType front()() return @trusted {
2152 			// cast to head-const for class key
2153 			return (cast(typeof(return))_table._store.elts[_binIndex]);
2154 		}
2155 	}
2156 	else {
2157 		/// Get reference to front element.
2158 		@property scope auto ref front() return @trusted {
2159 			return *(cast(const(SomeMap.ElementType)*)&_table._store.elts[_binIndex]); // propagate constnes
2160 		}
2161 	}
2162 	import core.internal.traits : Unqual;
2163 	// unqual to reduce number of instantations of `LvalueElementRef`
2164 	public LvalueElementRef!(Unqual!SomeMap) _elementRef;
2165 	alias _elementRef this;
2166 }
2167 
2168 /// Range over elements of r-value instance of this.
2169 static private struct ByRvalueElement(SomeMap) {
2170 	import nxt.container.traits : isAddress;
2171 	this(this) @disable;
2172 pragma(inline, true):
2173 	static if (isAddress!(SomeMap.ElementType)) // for reference types
2174 	{
2175 		/// Get reference to front element.
2176 		@property scope SomeMap.ElementType front()() return @trusted {
2177 			// cast to head-const for class key
2178 			return cast(typeof(return))_table._store.elts[_binIndex];
2179 		}
2180 	} else {
2181 		/// Get reference to front element.
2182 		@property auto ref front() return scope {
2183 			return *(cast(const(SomeMap.ElementType)*)&_table._store.elts[_binIndex]); // propagate constnes
2184 		}
2185 	}
2186 	import core.internal.traits : Unqual;
2187 	public RvalueElementRef!(Unqual!SomeMap) _elementRef;
2188 	alias _elementRef this;
2189 }
2190 
2191 /** Returns: range that iterates through the elements of `c` in undefined order.
2192  */
2193 auto byElement(SomeMap)(auto ref return SomeMap c) @trusted
2194 if (is(SomeMap == HybridHashMap!(_), _...) &&
2195 	!SomeMap.hasValue) {
2196 	import core.internal.traits : Unqual;
2197 	alias M = Unqual!SomeMap;
2198 	alias C = const(SomeMap);		// be const for now
2199 	static if (__traits(isRef, c)) // `c` is an l-value
2200 		auto result = ByLvalueElement!C((LvalueElementRef!(M)(cast(M*)&c)));
2201 	else { // `c` was is an r-value and can be moved
2202 		import core.lifetime : move;
2203 		auto result = ByRvalueElement!C((RvalueElementRef!(M)(move(*(cast(M*)&c))))); // reinterpret
2204 	}
2205 	result.findNextNonEmptyBin();
2206 	return result;
2207 }
2208 alias range = byElement;		// EMSI-container naming
2209 
2210 static private struct ByKey_lvalue(SomeMap)
2211 if (is(SomeMap == HybridHashMap!(_), _...) &&
2212 	SomeMap.hasValue) {
2213 	@property auto ref front() const return scope // key access must be const, TODO: auto ref => ref K
2214 	{
2215 		version (D_Coverage) {} else pragma(inline, true);
2216 		return _table._store.elts[_binIndex].key;
2217 	}
2218 	import core.internal.traits : Unqual;
2219 	public LvalueElementRef!(Unqual!SomeMap) _elementRef;
2220 	alias _elementRef this;
2221 }
2222 
2223 static private struct ByKey_rvalue(SomeMap)
2224 if (is(SomeMap == HybridHashMap!(_), _...) &&
2225 	SomeMap.hasValue) {
2226 	@property auto ref front() const return scope // key access must be const, TODO: auto ref => ref K
2227 	{
2228 		version (D_Coverage) {} else pragma(inline, true);
2229 		return _table._store.elts[_binIndex].key;
2230 	}
2231 	import core.internal.traits : Unqual;
2232 	public RvalueElementRef!(Unqual!SomeMap) _elementRef;
2233 	alias _elementRef this;
2234 }
2235 
2236 /** Returns: range that iterates through the keys of `c` in undefined order.
2237  */
2238 auto byKey(SomeMap)(auto ref /*TODO: return*/ SomeMap c) @trusted
2239 if (is(SomeMap == HybridHashMap!(_), _...) &&
2240 	SomeMap.hasValue) {
2241 	import core.internal.traits : Unqual;
2242 	alias M = Unqual!SomeMap;
2243 	alias C = const(SomeMap);		// be const
2244 	static if (__traits(isRef, c)) // `c` is an l-value
2245 		auto result = ByKey_lvalue!C((LvalueElementRef!(M)(cast(M*)&c)));
2246 	else { // `c` was is an r-value and can be moved
2247 		import core.lifetime : move;
2248 		auto result = ByKey_rvalue!C((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret
2249 	}
2250 	result.findNextNonEmptyBin();
2251 	return result;
2252 }
2253 
2254 static private struct ByValue_lvalue(SomeMap)
2255 if (is(SomeMap == HybridHashMap!(_), _...) &&
2256 	SomeMap.hasValue) {
2257 	@property scope auto ref front() return @trusted /+ TODO: auto ref => ref V +/
2258 	{
2259 		version (D_Coverage) {} else pragma(inline, true);
2260 		/+ TODO: functionize +/
2261 		import std.traits : isMutable;
2262 		static if (isMutable!(SomeMap)) /+ TODO: can this be solved without this `static if`? +/
2263 			alias E = SomeMap.ValueType;
2264 		else
2265 			alias E = const(SomeMap.ValueType);
2266 		return *(cast(E*)&_table._store.elts[_binIndex].value);
2267 	}
2268 	import core.internal.traits : Unqual;
2269 	public LvalueElementRef!(Unqual!SomeMap) _elementRef;
2270 	alias _elementRef this;
2271 }
2272 
2273 static private struct ByValue_rvalue(SomeMap)
2274 if (is(SomeMap == HybridHashMap!(_), _...) &&
2275 	SomeMap.hasValue) {
2276 	@property scope auto ref front() return @trusted /+ TODO: auto ref => ref V +/
2277 	{
2278 		version (D_Coverage) {} else pragma(inline, true);
2279 		/+ TODO: functionize +/
2280 		import std.traits : isMutable;
2281 		static if (isMutable!(SomeMap)) /+ TODO: can this be solved without this `static if`? +/
2282 			alias E = SomeMap.ValueType;
2283 		else
2284 			alias E = const(SomeMap.ValueType);
2285 		return *(cast(E*)&_table._store.elts[_binIndex].value);
2286 	}
2287 	import core.internal.traits : Unqual;
2288 	public RvalueElementRef!(Unqual!SomeMap) _elementRef;
2289 	alias _elementRef this;
2290 }
2291 
2292 /** Returns: range that iterates through the values of `c` in undefined order.
2293  */
2294 auto byValue(SomeMap)(auto ref return SomeMap c) @trusted
2295 if (is(SomeMap == HybridHashMap!(_), _...) &&
2296 	SomeMap.hasValue) {
2297 	import core.internal.traits : Unqual;
2298 	import std.traits : isMutable;
2299 	alias M = Unqual!SomeMap;
2300 	alias C = const(SomeMap);
2301 	static if (__traits(isRef, c)) // `c` is an l-value
2302 		auto result = ByValue_lvalue!SomeMap((LvalueElementRef!(M)(cast(M*)&c)));
2303 	else						// `c` was is an r-value and can be moved
2304 	{
2305 		import core.lifetime : move;
2306 		auto result = ByValue_rvalue!C((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret
2307 	}
2308 	result.findNextNonEmptyBin();
2309 	return result;
2310 }
2311 
2312 static private struct ByKeyValue_lvalue(SomeMap)
2313 if (is(SomeMap == HybridHashMap!(_), _...) &&
2314 	SomeMap.hasValue) {
2315 	@property scope auto ref front() return @trusted /+ TODO: auto ref => ref T +/
2316 	{
2317 		version (D_Coverage) {} else pragma(inline, true);
2318 		/+ TODO: functionize +/
2319 		import std.traits : isMutable;
2320 		static if (isMutable!(SomeMap))
2321 			alias E = SomeMap.KeyValueType;
2322 		else
2323 			alias E = const(SomeMap.T);
2324 		return *(cast(E*)&_table._store.elts[_binIndex]);
2325 	}
2326 	import core.internal.traits : Unqual;
2327 	public LvalueElementRef!(Unqual!SomeMap) _elementRef;
2328 	alias _elementRef this;
2329 }
2330 
2331 /** Returns: range that iterates through the key-value-pairs of `c` in undefined order.
2332  */
2333 auto byKeyValue(SomeMap)(auto ref return SomeMap c) @trusted
2334 if (is(SomeMap == HybridHashMap!(_), _...) &&
2335 	SomeMap.hasValue) {
2336 	import core.internal.traits : Unqual;
2337 	alias M = Unqual!SomeMap;
2338 	static if (__traits(isRef, c)) // `c` is an l-value
2339 		auto result = ByKeyValue_lvalue!SomeMap((LvalueElementRef!(M)(cast(M*)&c)));
2340 	else						// `c` was is an r-value and can be moved
2341 	{
2342 		import core.lifetime : move;
2343 		auto result = ByKeyValue_rvalue!SomeMap((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret
2344 	}
2345 	result.findNextNonEmptyBin();
2346 	return result;
2347 }
2348 
2349 /// make range from l-value and r-value. element access is always const
2350 pure @safe unittest {
2351 	import core.exception : AssertError;
2352 	import std.typecons : Nullable;
2353 	import nxt.digest.fnv : FNV;
2354 	import nxt.array_help : s;
2355 	debug import std.exception : assertThrown;
2356 
2357 	import std.algorithm.searching : count;
2358 	alias K = Nullable!(uint, uint.max);
2359 	alias X = HybridHashSet!(K, FNV!(64, true), defaultKeyEqualPredOf!K, Mallocator, Options(GrowOnlyFlag.no, BorrowCheckFlag.yes));
2360 
2361 	auto k11 = K(11);
2362 	auto k22 = K(22);
2363 	auto k33 = K(33);
2364 	auto ks = [k11, k22, k33].s;
2365 	auto k44 = K(44);
2366 
2367 	// mutable
2368 	auto x = X.withElements(ks);
2369 	assert(!x.contains(k44));
2370 	assert(!x.containsUsingLinearSearch(k44));
2371 	assert(x.length == 3);
2372 
2373 	assert(x.byElement.count == x.length);
2374 	foreach (e; x.byElement)	// from l-value
2375 	{
2376 		debug static assert(is(typeof(e) == const(K))); // always const access
2377 
2378 		// range invalidation forbidden:
2379 		debug
2380 		{
2381 			assertThrown!AssertError(x.reserveExtra(1));  // range invalidation
2382 			assertThrown!AssertError(x.clear());		  // range invalidation
2383 			assertThrown!AssertError(x.insert(k11));	  // range invalidation
2384 			assertThrown!AssertError(x.insertN([k11].s)); // range invalidation
2385 			assertThrown!AssertError(x.remove(k11));	  // range invalidation
2386 		}
2387 
2388 		// allowed
2389 		assert(x.contains(e));
2390 		assert(x.containsUsingLinearSearch(e));
2391 
2392 		const eHit = e in x;
2393 		assert(eHit);		   // found
2394 		assert(*eHit is e);	 // and the value equals what we searched for
2395 
2396 		const eDup = x.dup;	 // duplication is `const` and allowed
2397 		assert(eDup == x);
2398 	}
2399 
2400 	// const
2401 	const y = X.withElements(ks);
2402 	assert(!x.contains(k44));
2403 	assert(!x.containsUsingLinearSearch(k44));
2404 	foreach (e; y.byElement)	// from l-value
2405 	{
2406 		auto _ = y.byElement;   // ok to read-borrow again
2407 		assert(y.contains(e));
2408 		assert(y.containsUsingLinearSearch(e));
2409 		debug static assert(is(typeof(e) == const(K)));
2410 	}
2411 
2412 	foreach (e; X.withElements([K(11)].s).byElement) // from r-value
2413 	{
2414 		assert(e == K(11));
2415 		debug static assert(is(typeof(e) == const(K))); // always const access
2416 	}
2417 }
2418 
2419 /// range checking
2420 pure @safe unittest {
2421 	import core.exception : RangeError;
2422 	import std.typecons : Nullable;
2423 	import nxt.digest.fnv : FNV;
2424 	debug import std.exception : assertThrown, assertNotThrown;
2425 	immutable n = 11;
2426 
2427 	alias K = Nullable!(uint, uint.max);
2428 	alias V = uint;
2429 
2430 	alias X = HybridHashMap!(K, V, FNV!(64, true));
2431 
2432 	auto s = X.withCapacity(n);
2433 
2434 	void dummy(ref V value) {}
2435 
2436 	debug assertThrown!RangeError(dummy(s[K(0)]));
2437 
2438 	foreach (immutable i; 0 .. n) {
2439 		const k = K(i);
2440 		s[k] = V(i);
2441 		debug assertNotThrown!RangeError(dummy(s[k]));
2442 	}
2443 
2444 	foreach (immutable i; 0 .. n) {
2445 		const k = K(i);
2446 		assert(s.remove(k));
2447 		debug assertThrown!RangeError(dummy(s[k]));
2448 	}
2449 
2450 	s[K(0)] = V.init;
2451 	auto vp = K(0) in s;
2452 	debug static assert(is(typeof(vp) == V*));
2453 	assert((*vp) == V.init);
2454 
2455 	assert(s.remove(K(0)));
2456 	assert(K(0) !in s);
2457 
2458 	X t;
2459 	t.reserveExtra(4096);
2460 
2461 	t.clear();
2462 }
2463 
2464 /// class as value
2465 pure @safe unittest {
2466 	import core.exception : RangeError;
2467 	import std.typecons : Nullable;
2468 	debug import std.exception : assertThrown, assertNotThrown;
2469 	import nxt.digest.fnv : FNV;
2470 
2471 	immutable n = 11;
2472 
2473 	alias K = Nullable!(uint, uint.max);
2474 	class V
2475 	{
2476 		this(uint data) { this.data = data; }
2477 		uint data;
2478 	}
2479 
2480 	alias X = HybridHashMap!(K, V, FNV!(64, true));
2481 
2482 	auto s = X.withCapacity(n);
2483 
2484 	void dummy(ref V value) {}
2485 
2486 	debug assertThrown!RangeError(dummy(s[K(0)]));
2487 
2488 	foreach (immutable i; 0 .. n) {
2489 		const k = K(i);
2490 		s[k] = new V(i);
2491 		debug assertNotThrown!RangeError(dummy(s[k]));
2492 	}
2493 
2494 	// test range
2495 	{
2496 		auto sr = s.byKeyValue; // scoped range
2497 		assert(sr.length == n);
2498 		foreach (immutable i; 0 .. n) {
2499 			sr.popFront();
2500 			assert(sr.length == n - i - 1);
2501 		}
2502 	}
2503 
2504 	foreach (immutable i; 0 .. n) {
2505 		const k = K(i);
2506 		assert(s.remove(k));
2507 		debug assertThrown!RangeError(dummy(s[k]));
2508 	}
2509 
2510 	s[K(0)] = V.init;
2511 	auto vp = K(0) in s;
2512 	debug static assert(is(typeof(vp) == V*));
2513 
2514 	assert(s.remove(K(0)));
2515 	assert(K(0) !in s);
2516 
2517 	X t;
2518 	t.reserveExtra(4096);
2519 }
2520 
2521 /// constness inference of ranges
2522 pure nothrow unittest {
2523 	import std.typecons : Nullable;
2524 	import nxt.digest.fnv : FNV;
2525 
2526 	alias K = Nullable!(uint, uint.max);
2527 	class V
2528 	{
2529 		this(uint data) { this.data = data; }
2530 		uint data;
2531 	}
2532 
2533 	alias X = HybridHashMap!(K, V, FNV!(64, true));
2534 	const x = X();
2535 
2536 	foreach (const e; x.byKey) {
2537 		debug static assert(is(typeof(e) == const(X.KeyType)));
2538 	}
2539 
2540 	foreach (const e; x.byValue) {
2541 		debug static assert(is(typeof(e) == const(X.ValueType)));
2542 	}
2543 
2544 	foreach (const e; X.init.byValue) {
2545 		debug static assert(is(typeof(e) == const(X.ValueType)));
2546 	}
2547 
2548 	foreach (const e; x.byKeyValue) {
2549 		debug static assert(is(typeof(e.key) == const(X.KeyType)));
2550 		debug static assert(is(typeof(e.value) == const(X.ValueType)));
2551 		debug static assert(is(typeof(e) == const(X.ElementType)));
2552 	}
2553 }
2554 
2555 /// range key constness and value mutability with `class` value
2556 pure nothrow unittest {
2557 	import std.typecons : Nullable;
2558 	import nxt.digest.fnv : FNV;
2559 
2560 	struct S
2561 	{
2562 		uint value;
2563 	}
2564 	alias K = Nullable!(S, S(uint.min)); // use uint.min to trigger use of faster `allocator.allocateZeroed`
2565 
2566 	class V
2567 	{
2568 		this(uint data) { this.data = data; }
2569 		uint data;
2570 	}
2571 
2572 	alias X = HybridHashMap!(K, V, FNV!(64, true));
2573 	auto x = X();
2574 
2575 	x[K(S(42))] = new V(43);
2576 
2577 	assert(x.length == 1);
2578 
2579 	foreach (e; x.byValue)	  // `e` is auto ref
2580 	{
2581 		debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value
2582 		assert(e.data == 43);
2583 
2584 		// value mutation side effects
2585 		e.data += 1;
2586 		assert(e.data == 44);
2587 		e.data -= 1;
2588 		assert(e.data == 43);
2589 	}
2590 
2591 	foreach (ref e; x.byKeyValue)   // `e` is auto ref
2592 	{
2593 		debug static assert(is(typeof(e.key) == const(X.KeyType))); // const access to key
2594 		debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value
2595 
2596 		assert(e.key.value == 42);
2597 		assert(e.value.data == 43);
2598 
2599 		// key cannot be mutated
2600 		debug static assert(!__traits(compiles, { e.key.value += 1; }));
2601 
2602 		// value mutation side effects
2603 		e.value.data += 1;
2604 		assert(e.value.data == 44);
2605 		e.value.data -= 1;
2606 		assert(e.value.data == 43);
2607 	}
2608 }
2609 
2610 /// range key constness and value mutability with `class` key and `class` value
2611 pure nothrow unittest {
2612 	import nxt.digest.fnv : FNV;
2613 
2614 	class K
2615 	{
2616 		this(uint value) {
2617 			this.value = value;
2618 		}
2619 
2620 		@property bool opEquals(in typeof(this) rhs) const
2621 		{
2622 			return value == rhs.value;
2623 		}
2624 
2625 		uint value;
2626 	}
2627 
2628 	class V
2629 	{
2630 		this(uint data) { this.data = data; }
2631 		uint data;
2632 	}
2633 
2634 	alias X = HybridHashMap!(K, V, FNV!(64, true));
2635 	auto x = X();
2636 
2637 	x[new K(42)] = new V(43);
2638 
2639 	assert(x.length == 1);
2640 
2641 	foreach (e; x.byValue)	  // `e` is auto ref
2642 	{
2643 		debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value
2644 		assert(e.data == 43);
2645 
2646 		// value mutation side effects
2647 		e.data += 1;
2648 		assert(e.data == 44);
2649 		e.data -= 1;
2650 		assert(e.data == 43);
2651 	}
2652 
2653 	foreach (ref e; x.byKeyValue)   // `e` is auto ref
2654 	{
2655 		debug static assert(is(typeof(e.key) == X.KeyType)); // mutable access to class key
2656 		debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value
2657 
2658 		assert(e.key.value == 42);
2659 		assert(e.value.data == 43);
2660 
2661 		// class key itself should not be mutable
2662 		debug static assert(!__traits(compiles, { e.key = null; }));
2663 
2664 		// members of key can be mutated
2665 		debug static assert(__traits(compiles, { e.key.value += 1; }));
2666 
2667 		// value mutation side effects
2668 		e.value.data += 1;
2669 		assert(e.value.data == 44);
2670 		e.value.data -= 1;
2671 		assert(e.value.data == 43);
2672 	}
2673 }
2674 
2675 /// range key constness and value mutability with `class` key and `class` value
2676 pure nothrow unittest {
2677 	import nxt.digest.fnv : FNV;
2678 	class K
2679 	{
2680 		this(uint value) scope {
2681 			this.value = value;
2682 		}
2683 		uint value;
2684 	}
2685 
2686 	struct V
2687 	{
2688 		this(uint data) { this.data = data; }
2689 		this(this) @disable;
2690 		uint data;
2691 	}
2692 
2693 	alias X = HybridHashMap!(K, V, FNV!(64, true));
2694 	auto x = X();
2695 
2696 	scope key42 = new K(42);
2697 	() @trusted { x[key42] = V(43); }(); // TODO: qualify `HybridHashMap.opIndexAssign` with @trusted and remove
2698 
2699 	assert(x.length == 1);
2700 
2701 	foreach (ref e; x.byValue)  // `e` is auto ref
2702 	{
2703 		debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value
2704 		assert(e.data == 43);
2705 
2706 		// value mutation side effects
2707 		e.data += 1;
2708 		assert(e.data == 44);
2709 		e.data -= 1;
2710 		assert(e.data == 43);
2711 	}
2712 
2713 	foreach (ref e; x.byKeyValue) // `e` is auto ref
2714 	{
2715 		debug static assert(is(typeof(e.key) == X.KeyType)); // mutable access to class key
2716 		debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value
2717 
2718 		assert(e.key.value == 42);
2719 		assert(e.value.data == 43);
2720 
2721 		// value mutation side effects
2722 		e.value.data += 1;
2723 		assert(e.value.data == 44);
2724 		e.value.data -= 1;
2725 		assert(e.value.data == 43);
2726 	}
2727 
2728 	assert(x.length == 1);
2729 
2730 	assert(x.remove(key42));
2731 	assert(x.length == 0);
2732 
2733 	() @trusted { x[key42] = V(43); }(); // TODO: qualify `HybridHashMap.opIndexAssign` with @trusted and remove
2734 	assert(x.length == 1);
2735 }
2736 
2737 version (unittest) {
2738 	T make(T)(ulong value) {
2739 		static if (is(T == class))
2740 			return new T(value);
2741 		else
2742 			return T(value);
2743 	}
2744 }
2745 
2746 /// test various things
2747 @trusted unittest {
2748 	import std.meta : AliasSeq;
2749 	import std.typecons : Nullable;
2750 	import std.algorithm.comparison : equal;
2751 	import nxt.container.traits : mustAddGCRange;
2752 	import nxt.digest.fnv : FNV;
2753 	import nxt.array_help : s;
2754 
2755 	const n = 100;
2756 
2757 	void testEmptyAll(K, V, X)(ref X x, size_t n,
2758 							   scope K[] keys) {
2759 		assert(x.length == n);
2760 		foreach (key; keys) {
2761 			static if (X.hasValue)
2762 				const element = X.ElementType(key, V.init);
2763 			else
2764 				alias element = key;
2765 
2766 			assert(x.length == n - key.get);
2767 
2768 			const hitPtr = key in x;
2769 			static if (X.hasValue)
2770 				assert(hitPtr && *hitPtr is element.value);
2771 			else
2772 				assert(hitPtr && *hitPtr is element);
2773 
2774 			assert(x.remove(key));
2775 			assert(x.length == n - key.get - 1);
2776 
2777 			static if (!X.hasValue) {
2778 				assert(!x.contains(key));
2779 				assert(!x.containsUsingLinearSearch(key));
2780 			}
2781 			assert(key !in x);
2782 			assert(!x.remove(key));
2783 			assert(x.length == n - key.get - 1);
2784 		}
2785 
2786 		assert(x.length == 0);
2787 
2788 		x.clear();
2789 		assert(x.length == 0);
2790 	}
2791 
2792 	X testDup(X)(scope ref X x, size_t n) {
2793 		typeof(return) y = x.dup;
2794 
2795 		assert(x._store.elts.ptr !is y._store.elts.ptr);
2796 		assert(x.length == y.length);
2797 		assert(y.length == n);
2798 		// non-symmetric algorithm so both are needed
2799 		assert(y == x);
2800 		assert(x == y);
2801 
2802 		static if (X.hasValue) {
2803 			assert(equal(x.byKey,
2804 						 y.byKey));
2805 			assert(equal(x.byValue,
2806 						 y.byValue));
2807 			auto a = x.byKeyValue;
2808 			auto b = y.byKeyValue;
2809 			size_t i = 0;
2810 			while (!a.empty &&
2811 				   !b.empty) {
2812 				a.popFront();
2813 				b.popFront();
2814 				i++;
2815 			}
2816 			auto xR = x.byKeyValue;
2817 			auto yR = y.byKeyValue;
2818 			assert(xR.length == yR.length);
2819 			size_t ix = 0;
2820 			while (!xR.empty &&
2821 				   !yR.empty) {
2822 				auto xK = xR.front.key;
2823 				auto yK = yR.front.key;
2824 				auto xV = xR.front.value;
2825 				auto yV = yR.front.value;
2826 				// import std.stdio : writeln;
2827 				// writeln("ix:", ix, " xV:", xV, " yV:", yV);
2828 				assert(xK == yK);
2829 				assert(xV == yV);
2830 				assert(xR.front == yR.front);
2831 				xR.popFront();
2832 				yR.popFront();
2833 				ix++;
2834 			}
2835 			assert(equal(x.byKeyValue,
2836 						 y.byKeyValue));
2837 		}
2838 		else
2839 			assert(equal(x.byElement,
2840 						 y.byElement));
2841 
2842 		debug static assert(!__traits(compiles, { const _ = x < y; })); // no ordering
2843 
2844 		return y;
2845 	}
2846 
2847 	alias NullableUlong = Nullable!(ulong, ulong.max);
2848 
2849 	static class SomeSimpleClass
2850 	{
2851 		pure nothrow @safe @nogc
2852 		this(ulong value) {
2853 			this._value = value;
2854 		}
2855 
2856 		pure nothrow @safe @nogc
2857 		ulong get() const => _value;
2858 
2859 		void toString(Sink)(ref scope Sink sink) const {
2860 			import std.format : formattedWrite;
2861 			sink.formattedWrite(typeof(this).stringof, "(%s)", _value);
2862 		}
2863 
2864 		@property bool opEquals(in typeof(this) rhs) => _value == rhs._value;
2865 
2866 		private ulong _value;
2867 	}
2868 
2869 	debug static assert(mustAddGCRange!string);
2870 
2871 	foreach (K; AliasSeq!(SomeSimpleClass,
2872 						  NullableUlong)) {
2873 		foreach (V; AliasSeq!(string, int, void)) {
2874 			alias X = HybridHashMap!(K, V, FNV!(64, true));
2875 
2876 			static if (!X.hasValue) {
2877 				auto k11 = make!K(11);
2878 				auto k12 = make!K(12);
2879 				auto k13 = make!K(13);
2880 
2881 				auto x = X.withElements([k11, k12, k13].s);
2882 
2883 				import std.algorithm : count;
2884 
2885 				// ByLvalueElement
2886 				auto xr = x.byElement;
2887 
2888 				alias R = typeof(xr);
2889 				import std.range.primitives : isInputRange;
2890 				import std.traits : ReturnType;
2891 				debug static assert(is(typeof(R.init) == R));
2892 				debug static assert(is(ReturnType!((R xr) => xr.empty) == bool));
2893 
2894 				/+ TODO: Is this needed? debug static assert(!__traits(compiles, { xr.front == K.init; })); // always head-const +/
2895 				auto f = xr.front;
2896 				static if (is(K == class)) {
2897 					debug static assert(is(typeof(f) == K)); // tail-mutable
2898 				}
2899 				else
2900 				{
2901 					debug static assert(is(typeof(f) == const(K))); // tail-const
2902 				}
2903 
2904 				debug static assert(is(typeof((R xr) => xr.front)));
2905 				debug static assert(!is(ReturnType!((R xr) => xr.front) == void));
2906 				debug static assert(is(typeof((R xr) => xr.popFront)));
2907 
2908 				debug static assert(isInputRange!(typeof(xr)));
2909 
2910 				assert(x.byElement.count == 3);
2911 
2912 				X y;
2913 				size_t ix = 0;
2914 				foreach (ref e; x.byElement) {
2915 					assert(x.contains(e));
2916 					assert(x.containsUsingLinearSearch(e));
2917 					assert(!y.contains(e));
2918 					assert(!y.containsUsingLinearSearch(e));
2919 					static if (is(K == class))
2920 						y.insert(cast(K)e); // ugly but ok in tests
2921 					else
2922 						y.insert(e);
2923 					assert(y.contains(e));
2924 					assert(y.containsUsingLinearSearch(e));
2925 					ix++;
2926 				}
2927 
2928 				assert(y.byElement.count == 3);
2929 				assert(x == y);
2930 
2931 				const z = X();
2932 				assert(z.byElement.count == 0);
2933 
2934 				immutable w = X();
2935 				assert(w.byElement.count == 0);
2936 
2937 				{
2938 					auto xc = X.withElements([k11, k12, k13].s);
2939 					assert(xc.length == 3);
2940 					assert(xc.contains(k11));
2941 					assert(xc.containsUsingLinearSearch(k11));
2942 
2943 					/+ TODO: http://forum.dlang.org/post/kvwrktmameivubnaifdx@forum.dlang.org +/
2944 					xc.removeAllMatching!(_ => _ == k11);
2945 
2946 					assert(xc.length == 2);
2947 					assert(!xc.contains(k11));
2948 					assert(!xc.containsUsingLinearSearch(k11));
2949 
2950 					xc.removeAllMatching!(_ => _ == k12);
2951 					assert(!xc.contains(k12));
2952 					assert(!xc.containsUsingLinearSearch(k12));
2953 					assert(xc.length == 1);
2954 
2955 					xc.removeAllMatching!(_ => _ == k13);
2956 					assert(!xc.contains(k13));
2957 					assert(!xc.containsUsingLinearSearch(k13));
2958 					assert(xc.length == 0);
2959 
2960 					// this is ok
2961 					foreach (_; xc.byElement) {}
2962 				}
2963 
2964 				{			   // ByRvalueElement
2965 					auto k = X.withElements([k11, k12].s).filtered!(_ => _ != k11).byElement;
2966 					debug static assert(isInputRange!(typeof(k)));
2967 					assert(k.front == k12);
2968 
2969 					debug static assert(!__traits(compiles, { k.front = K.init; })); // head-const
2970 					static if (is(K == class)) {
2971 						debug static assert(is(typeof(k.front) == K)); // tail-mutable
2972 					}
2973 					else
2974 					{
2975 						debug static assert(is(typeof(k.front) == const(K))); // tail-const
2976 					}
2977 
2978 					k.popFront();
2979 					assert(k.empty);
2980 				}
2981 
2982 				{
2983 					X q;
2984 					auto qv = [make!K(11U), make!K(12U), make!K(13U), make!K(14U)].s;
2985 					q.insertN(qv[]);
2986 					foreach (e; qv[]) {
2987 						assert(q.contains(e));
2988 						assert(q.containsUsingLinearSearch(e));
2989 					}
2990 					q.clear();
2991 					assert(q.empty);
2992 				}
2993 			}
2994 
2995 			static if (is(V == string)) {
2996 				debug static assert(mustAddGCRange!V);
2997 				debug static assert(mustAddGCRange!(V[1]));
2998 				debug static assert(mustAddGCRange!(X.T));
2999 			}
3000 
3001 			auto x1 = X();			// start empty
3002 
3003 			// fill x1
3004 
3005 			import std.array : Appender;
3006 			Appender!(K[]) keys;
3007 
3008 			foreach (immutable key_; 0 .. n) {
3009 				auto key = make!K(key_);
3010 				keys.put(key);
3011 
3012 				// create elements
3013 				static if (X.hasValue) {
3014 					auto value = V.init;
3015 					auto element = X.ElementType(key, value);
3016 				}
3017 				else
3018 					// no assignment because Nullable.opAssign may leave rhs in null state
3019 					auto element = key;
3020 
3021 				assert(key !in x1);
3022 
3023 				assert(x1.length == key.get);
3024 				assert(x1.insert(element) == X.InsertionStatus.added);
3025 				assert(x1.length == key.get + 1);
3026 
3027 				static if (X.hasValue) {
3028 					import std.conv : to;
3029 					auto e2 = X.ElementType(key, (42 + key_).to!V);
3030 					assert(x1.insert(e2) == X.InsertionStatus.modified);
3031 					assert(x1.contains(key));
3032 					assert(x1.get(key, V.init) == (42 + key_).to!V);
3033 
3034 					assert(x1.remove(key));
3035 					assert(!x1.contains(key));
3036 
3037 					x1[key] = value; // restore value
3038 					assert(x1.contains(key));
3039 				}
3040 
3041 				assert(x1.length == key.get + 1);
3042 
3043 				const hitPtr = key in x1;
3044 				static if (X.hasValue)
3045 					assert(hitPtr && *hitPtr == value);
3046 				else
3047 					assert(hitPtr && *hitPtr is key);
3048 
3049 				auto status = x1.insert(element);
3050 				assert(status == X.InsertionStatus.unmodified);
3051 				static if (X.hasValue)
3052 					assert(x1.insert(key, value) == X.InsertionStatus.unmodified);
3053 				assert(x1.length == key.get + 1);
3054 
3055 				assert(key in x1);
3056 			}
3057 
3058 			static if (X.hasValue) {
3059 				import nxt.container.dynamic_array : Array = DynamicArray;
3060 				Array!(X.ElementType) a1; // remember the keys
3061 
3062 				foreach (const ref key; x1.byKey) {
3063 					auto keyPtr = key in x1;
3064 					assert(keyPtr);
3065 					a1 ~= X.ElementType(cast(K)key, (*keyPtr));
3066 				}
3067 
3068 				assert(x1.length == a1.length);
3069 
3070 				foreach (ae; a1[]) {
3071 					auto keyPtr = ae.key in x1;
3072 					assert(keyPtr);
3073 					assert((*keyPtr) is ae.value);
3074 				}
3075 			}
3076 
3077 			assert(x1.length == n);
3078 
3079 			auto x2 = testDup(x1, n);
3080 
3081 			testEmptyAll!(K, V)(x1, n, keys.data);
3082 
3083 			testEmptyAll!(K, V)(x2, n, keys.data); // should be not affected by emptying of x1
3084 		}
3085 	}
3086 }
3087 
3088 ///
3089 pure nothrow @safe @nogc unittest {
3090 	import std.typecons : Nullable;
3091 	import nxt.digest.fnv : FNV;
3092 
3093 	alias X = HybridHashMap!(Nullable!(size_t, size_t.max), size_t, FNV!(64, true));
3094 	X x;
3095 	assert(x.empty);
3096 	// import nxt.container.dynamic_array : Array = DynamicArray;
3097 	/+ TODO: these segfault: +/
3098 	/+ TODO: auto a = Array!(X.KeyType).withElementsOfRange_untested(x.byKey); // l-value byKey +/
3099 	/+ TODO: auto b = Array!(X.KeyType).withElementsOfRange_untested(X().byKey); // r-value byKey +/
3100 }
3101 
3102 /// manual Nullable type
3103 pure @safe unittest {
3104 	import nxt.nullable_traits : isNullable;
3105 	import nxt.digest.fnv : FNV;
3106 
3107 	static class Zing {
3108 		pure nothrow @safe @nogc:
3109 		this(ulong value) { this._value = value; }
3110 		private ulong _value;
3111 	}
3112 	debug static assert(isNullable!Zing);
3113 
3114 	enum Alt { unknown, a, b, c, d }
3115 
3116 	struct ZingRelation {
3117 		Zing zing;
3118 		Alt alts;
3119 
3120 		alias nullifier = zing;
3121 		static immutable nullValue = typeof(this).init;
3122 
3123 		bool opEquals(in typeof(this) that) const pure nothrow @safe @nogc
3124 			=> (this.zing is that.zing && this.alts == that.alts);
3125 	}
3126 	debug static assert(isNullable!ZingRelation);
3127 
3128 	alias X = HybridHashSet!(ZingRelation, FNV!(64, true));
3129 	debug static assert(X.sizeof == 24);
3130 	X x;
3131 
3132 	scope e = ZingRelation(new Zing(42), Alt.init);
3133 
3134 	assert(!x.contains(e));
3135 	assert(!x.containsUsingLinearSearch(e));
3136 	assert(x.insert(e) == X.InsertionStatus.added);
3137 	assert(x.contains(e));
3138 	assert(x.containsUsingLinearSearch(e));
3139 }
3140 
3141 /// abstract class value type
3142 @safe unittest {
3143 	static abstract class Zing {
3144 		pure nothrow @safe @nogc:
3145 	}
3146 	static class Node : Zing {
3147 		pure nothrow @safe @nogc:
3148 	}
3149 
3150 	alias X = HybridHashSet!(Zing);
3151 	X x;
3152 
3153 	const Zing cz = new Node();
3154 	x.insert(cz);			   // ok to insert const
3155 
3156 	Zing z = new Node();
3157 	x.insert(z); // ok to insert mutable because hashing is on address by default
3158 }
3159 
3160 /// class type with default hashing
3161 @safe unittest {
3162 	static class Base {
3163 		static size_t dtorCount = 0; // number of calls to this destructor
3164 	@safe nothrow @nogc:
3165 		~this() nothrow @nogc { dtorCount += 1; }
3166 	pure:
3167 		this(ulong value) { this._value = value; }
3168 		@property bool opEquals(in typeof(this) rhs) const => _value == rhs._value;
3169 		override hash_t toHash() const => hashOf(_value);
3170 		private ulong _value;
3171 	}
3172 
3173 	/** Node containing same data members but different type. */
3174 	static class Node : Base {
3175 		pure nothrow @safe @nogc:
3176 		this(ulong value) { super(value);  }
3177 	}
3178 	debug static assert(is(Node : Base));
3179 
3180 	import nxt.hash_functions : hashOfPolymorphic; // neede to separate hash of `Base(N)` from `Node(N)`
3181 	alias X = HybridHashSet!(Base, hashOfPolymorphic, "a && b && (typeid(a) is typeid(b)) && a.opEquals(b)");
3182 	debug static assert(X.sizeof == 24);
3183 	X x;
3184 
3185 	// top-class
3186 	auto b42 = new Base(42);	/+ TODO: qualify as scope when hashOf parameter arg is scope +/
3187 	assert(!x.contains(b42));
3188 	assert(!x.containsUsingLinearSearch(b42));
3189 	assert(x.insert(b42) == X.InsertionStatus.added);
3190 	assert(x.contains(b42));
3191 	assert(x.containsUsingLinearSearch(b42));
3192 	assert(x.tryGetElementFromCtorParams!Base(42) !is null);
3193 	assert(Base.dtorCount == 1);
3194 	assert(x.tryGetElementFromCtorParams!Base(42)._value == 42);
3195 	assert(Base.dtorCount == 2);
3196 	assert(x.tryGetElementFromCtorParams!Base(41) is null);
3197 	assert(Base.dtorCount == 3);
3198 
3199 	// top-class
3200 	auto b43 = new Base(43);	/+ TODO: qualify as scope when hashOf parameter arg is scope +/
3201 	assert(!x.contains(b43));
3202 	assert(!x.containsUsingLinearSearch(b43));
3203 	assert(x.insert(b43) == X.InsertionStatus.added);
3204 	assert(x.contains(b43));
3205 	assert(x.containsUsingLinearSearch(b43));
3206 	assert(x.tryGetElementFromCtorParams!Base(43) !is null);
3207 	assert(Base.dtorCount == 4);
3208 	assert(x.tryGetElementFromCtorParams!Base(43)._value == 43);
3209 	assert(Base.dtorCount == 5);
3210 
3211 	// sub-class
3212 	assert(x.tryGetElementFromCtorParams!Node(42) is null);
3213 	assert(Base.dtorCount == 6);
3214 	immutable n42 = new Node(42);
3215 	assert(!x.contains(n42));	 // mustn't equal to `b42`
3216 	assert(!x.containsUsingLinearSearch(n42)); // mustn't equal to `b42`
3217 	assert(x.insert(n42) == X.InsertionStatus.added); // added as separate type
3218 	assert(x.contains(n42));
3219 	assert(x.containsUsingLinearSearch(n42));
3220 	assert(x.tryGetElementFromCtorParams!Node(42) !is null);
3221 	assert(Base.dtorCount == 7);
3222 	assert(x.tryGetElementFromCtorParams!Node(42)._value == 42);
3223 	assert(Base.dtorCount == 8);
3224 
3225 	assert(hashOf(b42) == hashOf(n42));
3226 
3227 	// sub-class
3228 	assert(x.tryGetElementFromCtorParams!Node(43) is null);
3229 	assert(Base.dtorCount == 9);
3230 	auto n43 = new Node(43);
3231 	assert(!x.contains(n43));	 // mustn't equal to `b43`
3232 	assert(!x.containsUsingLinearSearch(n43)); // mustn't equal to `b43`
3233 	assert(x.insert(n43) == X.InsertionStatus.added); // added as separate type
3234 	assert(x.contains(n43));
3235 	assert(x.containsUsingLinearSearch(n43));
3236 	assert(x.tryGetElementFromCtorParams!Node(43) !is null);
3237 	assert(Base.dtorCount == 10);
3238 	assert(x.tryGetElementFromCtorParams!Node(43)._value == 43);
3239 	assert(Base.dtorCount == 11);
3240 
3241 	assert(hashOf(b43) == hashOf(n43));
3242 }
3243 
3244 /// enumeration key
3245 pure @safe unittest {
3246 	import nxt.digest.fnv : FNV;
3247 
3248 	enum Alt {
3249 		nullValue,			  // trait
3250 		a, b, c, d
3251 	}
3252 	alias X = HybridHashSet!(Alt, FNV!(64, true));
3253 	X x;
3254 	assert(!x.contains(Alt.a));
3255 
3256 	assert(x.insert(Alt.a) == X.InsertionStatus.added);
3257 
3258 	assert(x.contains(Alt.a));
3259 	assert(x.containsUsingLinearSearch(Alt.a));
3260 	assert(!x.contains(Alt.b));
3261 	assert(!x.contains(Alt.c));
3262 	assert(!x.contains(Alt.d));
3263 	assert(!x.containsUsingLinearSearch(Alt.b));
3264 	assert(!x.containsUsingLinearSearch(Alt.c));
3265 	assert(!x.containsUsingLinearSearch(Alt.d));
3266 
3267 	assert(x.remove(Alt.a));
3268 	assert(!x.contains(Alt.a));
3269 	assert(!x.containsUsingLinearSearch(Alt.a));
3270 }
3271 
3272 ///
3273 pure nothrow @safe unittest {
3274 	import nxt.digest.fnv : FNV;
3275 	static struct Rel {
3276 		static immutable nullValue = typeof(this).init;
3277 		string name;			// relation name. WARNING compiler crashes when qualified with `package`
3278 	}
3279 	alias X = HybridHashSet!(Rel, FNV!(64, true));
3280 	X x;
3281 	foreach (const i; 0 .. 100) {
3282 		const char[1] ch = ['a' + i];
3283 		assert(!x.contains(Rel(ch.idup)));
3284 		assert(!x.containsUsingLinearSearch(Rel(ch.idup)));
3285 		x.insert(Rel(ch.idup));
3286 		assert(x.contains(Rel(ch.idup)));
3287 		/* TODO: assert(x.containsUsingLinearSearch(Rel(ch.idup))); */
3288 	}
3289 }
3290 
3291 /// `SSOString` as set key type
3292 pure nothrow @safe @nogc unittest {
3293 	import nxt.sso_string : SSOString;
3294 	import nxt.digest.fnv : FNV;
3295 
3296 	alias K = SSOString;
3297 	static assert(isHoleable!K);
3298 	alias X = HybridHashSet!(K, FNV!(64, true));
3299 	const n = 100;
3300 
3301 	X a;
3302 	foreach (const i; 0 .. n) {
3303 		const char[1] ch = ['a' + i];
3304 		const k = K(ch);		// @nogc
3305 
3306 		assert(!a.contains(k));
3307 		assert(!a.containsUsingLinearSearch(k));
3308 
3309 		assert(a.insert(K(ch)) == X.InsertionStatus.added);
3310 		/+ TODO: assert(a.insertAndReturnElement(K(ch)) == k); +/
3311 		assert(a.contains(k));
3312 		assert(a.containsUsingLinearSearch(k));
3313 
3314 		assert(a.remove(k));
3315 		assert(!a.contains(k));
3316 		assert(a.insert(K(ch)) == X.InsertionStatus.added);
3317 
3318 		assert(a.remove(ch[]));
3319 		assert(!a.contains(k));
3320 		assert(a.insert(K(ch)) == X.InsertionStatus.added);
3321 	}
3322 
3323 	X b;
3324 	foreach (const i; 0 .. n) {
3325 		const char[1] ch = ['a' + (n - 1 - i)];
3326 		const k = K(ch);		// @nogc
3327 
3328 		assert(!b.contains(k));
3329 		assert(!b.containsUsingLinearSearch(k));
3330 
3331 		assert(b.insert(K(ch)) == X.InsertionStatus.added);
3332 		/+ TODO: assert(b.insertAndReturnElement(K(ch)) == k); +/
3333 
3334 		assert(b.contains(k));
3335 		assert(b.containsUsingLinearSearch(k));
3336 
3337 		assert(b.remove(k));
3338 		assert(!b.contains(k));
3339 
3340 		assert(b.insert(K(ch)) == X.InsertionStatus.added);
3341 	}
3342 
3343 	assert(a == b);
3344 
3345 	const us = K("_");
3346 	assert(!a.contains(us));
3347 	a ~= us;
3348 	assert(a.contains(us));
3349 }
3350 
3351 /// test `opIndexOpAssign`
3352 pure nothrow @safe unittest {
3353 	import nxt.sso_string : SSOString;
3354 	import nxt.digest.fnv : FNV;
3355 
3356 	alias K = SSOString;
3357 	alias V = long;
3358 	alias X = HybridHashMap!(K, V, FNV!(64, true));
3359 
3360 	X x;
3361 
3362 	const a = K("a");
3363 	const b = K("b");
3364 
3365 	x[a] = 17;
3366 	assert(x[a] == 17);
3367 
3368 	x[a] += 10;				 // opIndexOpAssign!("+=") with existing key
3369 	assert(x[a] == 27);
3370 
3371 	x[b] += 10;				 // opIndexOpAssign!("+=") with non-existing key
3372 	assert(x[b] == 10);
3373 
3374 	x[b] *= 10;				 // opIndexOpAssign!("*=") with non-existing key
3375 	assert(x[b] == 100);
3376 
3377 	assert(x.length == 2);
3378 
3379 	assert(x.contains(a));
3380 	assert(x.contains(a[]));
3381 	() @trusted { assert(a in x); }(); /+ TODO: remove wrapper lambda +/
3382 	assert(a[] in x);
3383 
3384 	assert(x.contains(b));
3385 	assert(x.contains(b[]));
3386 	() @trusted { assert(b in x); }(); /+ TODO: remove wrapper lambda +/
3387 	assert(b[] in x);
3388 
3389 	const c = K("c");
3390 	assert(!x.contains(c));
3391 	assert(!x.contains(c[]));
3392 	assert(c !in x);
3393 	assert(c[] !in x);
3394 }
3395 
3396 /// use prime numbers as capacity
3397 version (none)					/+ TODO: enable +/
3398 pure @safe unittest {
3399 	import nxt.address : AlignedAddress;
3400 	alias K = AlignedAddress!1;
3401 	alias V = size_t;
3402 	alias M = HybridHashMap!(K, V, hashOf, defaultKeyEqualPredOf!K, Mallocator,
3403 							 BorrowCheckFlag.no, true, UsePrimeCapacityFlag.yes);
3404 	M x;
3405 	assert(x.empty);
3406 }
3407 
3408 /// `SSOString` as map key type
3409 pure nothrow @safe @nogc unittest {
3410 	import nxt.sso_string : SSOString;
3411 	import nxt.digest.fnv : FNV;
3412 	alias K = SSOString;
3413 	alias V = long;
3414 	alias X = HybridHashMap!(K, V, FNV!(64, true));
3415 	const n = 100;
3416 
3417 	immutable default_k = K("miss");
3418 
3419 	X a;
3420 
3421 	// insert all
3422 	foreach (const i; 0 .. n) {
3423 		const char[1] ch = ['a' + i];
3424 		const k = K(ch);		// @nogc
3425 		assert(k[] == ch[]);
3426 
3427 		assert(!a.contains(k));
3428 		assert(!a.contains(ch[]));						  // @nogc
3429 		assert(a.getKeyRef(k, default_k)[] is default_k[]); // on miss use `default_k`
3430 		/+ TODO: assert(a.getKeyRef(ch, default_k)[] is default_k[]); // on miss use `default_k` +/
3431 
3432 		a[k] = V.init;
3433 
3434 		assert(a.contains(k));
3435 		assert(a.contains(ch[]));					// @nogc
3436 		assert(a.getKeyRef(k, default_k)[] !is k[]); // on hit doesn't use `default_k`
3437 		assert(a.getKeyRef(k, default_k)[] == ch);
3438 		/+ TODO: assert(a.getKeyRef(ch, default_k)[] !is k[]); // on hit doesn't use `default_k` +/
3439 		// assert(a.getKeyRef(ch, default_k)[] == ch);
3440 	}
3441 	assert(a.length == n);
3442 
3443 	// remove all
3444 	foreach (const i; 0 .. n) {
3445 		const char[1] ch = ['a' + i];
3446 		const k = K(ch);		// @nogc
3447 		assert(a.contains(k));
3448 		assert(a.remove(k));
3449 		assert(!a.contains(k));
3450 	}
3451 	assert(a.length == 0);
3452 
3453 	// insert all again
3454 	foreach (const i; 0 .. n) {
3455 		const char[1] ch = ['a' + i];
3456 		const k = K(ch);		// @nogc
3457 		assert(k[] == ch[]);
3458 
3459 		assert(!a.contains(k));
3460 		assert(!a.contains(ch[]));						  // @nogc
3461 		assert(a.getKeyRef(k, default_k)[] is default_k[]); // on miss use `default_k`
3462 		/+ TODO: assert(a.getKeyRef(ch, default_k)[] is default_k[]); // on miss use `default_k` +/
3463 
3464 		a[k] = V.init;
3465 	}
3466 	assert(a.length == n);
3467 
3468 	X b;
3469 	foreach (const i; 0 .. n) {
3470 		const char[1] ch = ['a' + (n - 1 - i)];
3471 		const k = K(ch);		// @nogc
3472 
3473 		assert(!b.contains(k));
3474 
3475 		b[k] = V.init;
3476 
3477 		assert(b.contains(k));
3478 	}
3479 
3480 	assert(a == b);
3481 }
3482 
3483 ///
3484 pure nothrow @safe @nogc unittest {
3485 	import nxt.address : AlignedAddress;
3486 	alias A = AlignedAddress!1;
3487 	HybridHashMap!(A, A) m;
3488 	static assert(m.sizeof == 3*size_t.sizeof); // assure that hole bitmap is not used
3489 	foreach (const address; 1 .. 0x1000) {
3490 		const key = address;
3491 		const value = 2*address;
3492 		assert(A(key) !in m);
3493 		m[A(key)] = A(value);
3494 		const eq = m[A(key)] == A(value);
3495 		assert(eq);
3496 		assert(A(key) in m);
3497 	}
3498 }
3499 
3500 ///
3501 pure nothrow @safe @nogc unittest {
3502 	import nxt.sso_string : SSOString;
3503 	alias K = SSOString;
3504 	alias V = long;
3505 	alias X = HybridHashMap!(K, V, hashOf, defaultKeyEqualPredOf!(K), Mallocator, Options(GrowOnlyFlag.no, BorrowCheckFlag.no));
3506 	X x;
3507 }
3508 
3509 /// non-nullable key type
3510 version (none)					/+ TODO: enable +/
3511 pure nothrow @safe @nogc unittest {
3512 	alias K = long;
3513 	alias V = long;
3514 	alias X = HybridHashMap!(K, V, hashOf, defaultKeyEqualPredOf!(K), Mallocator, Options(GrowOnlyFlag.no, BorrowCheckFlag.no));
3515 	X x;
3516 }
3517 
3518 /** Is `true` iff `T` has a specific value dedicated to representing holes
3519  * (removed/erase) values.
3520  */
3521 enum isHoleable(T) = (// __traits(hasMember, T, "isHole") &&
3522 					  // __traits(hasMember, T, "holeify") &&
3523 	__traits(hasMember, T, "holeValue"));
3524 
3525 /** Default key equality/equivalence predicate for the type `T`.
3526  */
3527 template defaultKeyEqualPredOf(T) {
3528 	static if (is(T == class))
3529 		// static assert(__traits(hasMember, T, "opEquals"),
3530 		//			   "Type" ~ T.stringof ~ " doesn't have local opEquals() defined");
3531 		// enum defaultKeyEqualPredOf = "a && b && a.opEquals(b)";
3532 		enum defaultKeyEqualPredOf = "a is b";
3533 		// (const T a, const T b) => ((a !is null) && (b !is null) && a.opEquals(b));
3534 	else
3535 		enum defaultKeyEqualPredOf = "a == b";
3536 }
3537 
3538 ///
3539 pure nothrow @safe unittest {
3540 	class C {
3541 		pure nothrow @safe @nogc:
3542 		this(int x) {
3543 			this.x = x;
3544 		}
3545 		@property bool opEquals(in typeof(this) rhs) const => x == rhs.x;
3546 		@property override bool opEquals(const scope Object rhs) const @trusted {
3547 			C rhs_ = cast(C)rhs;
3548 			return rhs_ && x == rhs_.x;
3549 		}
3550 		int x;
3551 	}
3552 	static assert(defaultKeyEqualPredOf!(C) == "a is b");
3553 }