1 /** Tries and Prefix Trees.
2 
3 	Implementation is layered:
4 	$(UL
5 	$(LI `RawRadixTree` stores its untyped keys as variable length ubyte-strings (`UKey`))
6 	$(LI On top of that `RadixTree` implements typed-key access via its template parameter `Key`.)
7 	)
8 	Both layers currently support
9 	$(UL
10 	$(LI template parameterization on the `Value`-type in the map case (when `Value` is non-`void`))
11 	$(LI `@nogc` and, when possible, `@safe pure nothrow`)
12 	$(LI insertion with returned modifications status: `auto newKeyWasInserted = set.insert(Key key)`)
13 	$(LI AA-style `in`-operator)
14 	  $(UL
15 	  $(LI `key in set` is `bool` for set-case)
16 	  $(LI `key in map` returns non-`null` `value` pointer when `key` is stored in `map`)
17 	  )
18 	$(LI AA-style iteration of map keys: `map.byKey()`)
19 	$(LI AA-style iteration of map values: `map.byValue()`)
20 	$(LI `SortedRange`-style iteration over elements sorted by key: `assert(set[].isSorted)`)
21 	$(LI containment checking: `bool contains(in Key key)`)
22 	$(LI element indexing and element index assignment for map case via `opIndex` and `opIndexAssign`)
23 	$(LI key-prefix Completion (returning a `Range` over all set/map-elements that match a key prefix): `prefix(Key keyPrefix)`)
24 	)
25 	Typed layer supports
26 	$(UL
27 	$(LI ordered access of aggregate types)
28 	)
29 
30 	Test: dmd -version=show -preview=dip1000 -preview=in -vcolumns -preview=in -debug -g -unittest -checkaction=context -allinst -main -unittest -I../.. -i -run trie.d
31 
32 	See_Also: $(HTTP en.wikipedia.org/wiki/Trie)
33 	See_Also: $(HTTP en.wikipedia.org/wiki/Radix_tree)
34 	See_Also: $(HTTP github.com/nordlow/phobos-next/blob/master/src/test_trie_prefix.d) for a descriptive usage of prefixed access.
35 
36 	TODO: Get rid of explicit @nogc qualifiers for all templates starting with those taking A alloc as parameter
37 
38 	TODO: Try to get rid of alias this
39 
40 	TODO: use fast bit-scanning functions in core.bitop for DenseBranch and
41 	DenseLeaf iterators
42 
43 	TODO: shift addresses of class and pointers by its alignment before adding
44 	them to make top-branch as dense possible
45 
46 	TODO: Allow slicing from non-mutable tries and add test-case at line 5300
47 
48 	TODO: 2. Make as many members as possible free functionss free-functions to
49 	reduce compilation times. Alternative make them templates (`tlm` )
50 
51 	TODO: Use scope on `Range`, `RawRange` and members that return key and value
52 	reference when DIP-1000 has been implemented
53 
54 	TODO: Encode `string` with zero-terminating 0 byte when it's not the last
55 	member in a key aggregate
56 
57 	TODO: split up file into raw_trie.d, trie.d
58 
59 	TODO
60 
61 	Replace
62 	case undefined: return typeof(return).init; // terminate recursion
63 	with
64 	case undefined: return curr;
65 
66 	TODO:
67 	TODO: Make `Key` and Ix[]-array of `immutable Ix` like `string`
68 
69 	TODO: Allow `Node`-constructors to take const and immutable prefixes and then
70 	make `toRawKey` and `toTypedKey` accept return const-slices
71 
72 	TODO: Remove @trusted from VLA (variable length array)-members of SparseBranch/SparseLeaf and make their callers @trusted instead.
73 
74 	TODO: Assure that ~this() nothrow is run for argument `nt` in `freeNode`. Can we use `postblit()` for this?
75 
76 	TODO: Search for "functionize this loop or reuse memmove" and use move()
77 
78 	TODO: Add Branch-hint allocation flag and re-benchmark construction of `RadixTreeSet` with 10000000 uints
79 
80 	TODO: Add sortedness to `IxsN` and make `IxsN.contains()` use `binarySearch()`. Make use of `sortn`.
81 
82 	TODO: Check for case when expanding to bit-branch instead of `SparseBranch` in all `expand()` overloads
83 
84 	TODO: Make array indexing/slicing as @trusted and use .ptr[] instead of [] when things are stable.
85 
86 	TODO: Add various extra packings in MixLeaf1to4: number of
87 	- Ix  (0,1,2,3,4,5,6): 3-bits
88 	- Ix2 (0,1,2,3): 2-bits
89 	- Ix3 (0,1,2): 2-bits
90 	- Ix4 (0,1): 1-bit
91 	Total bits 8-bits
92 	Possible packings with 6 bytes
93 	- 4_
94 	- 4_2
95 	- 4_2
96 	- 4_2_1
97 	- 4_1
98 	- 4_1_1
99 	- 3_2
100 	- 3_2_1
101 	- 3_1
102 	- 3_1_1
103 	- 3_1_1_1
104 	- 2_2_2
105 	- 2_2
106 	- 2_2_1
107 	- 2_2_1_1
108 	- 2
109 	- 2_1
110 	- 2_1_1
111 	- 2_1_1_1
112 	- 2_1_1_1_1
113 	- 1
114 	- 1_1
115 	- 1_1_1
116 	- 1_1_1_1
117 	- 1_1_1_1_1
118 	- 1_1_1_1_1_1
119 
120 	TODO: Sorted Range Primitives over Keys
121 
122 	- Returns a range of elements which are equivalent (though not necessarily equal) to value.
123 	  auto equalRange(this This)(inout T value)
124 
125 	- Returns a range of elements which are greater than low and smaller than highValue.
126 	  auto bound(this This)(inout T lowValue, inout T highValue)
127 
128 	- Returns a range of elements which are less than value.
129 	  auto lowerBound(this This)(inout T value)
130 
131 	- Returns a range of elements which are greater than value.
132 	  auto upperBound(this This)(inout T value)
133 
134 	TODO: opBinaryRight shall return `_rawTree.ElementRef` instead of `bool`
135 
136 	TODO: Fix vla-allocations in makeVariableSizeNodePointer according
137 	C11-recommendations. For reference set commit
138 	d2f1971dd570439da4198fa76603b53b072060f8 at
139 	https://github.com/emacs-mirror/emacs.git
140 */
141 module nxt.container.trie;
142 
143 import std.traits : isPointer, Unqual;
144 import std.range.primitives : isInputRange, ElementType;
145 
146 import nxt.bijections : isIntegralBijectableType, bijectToUnsigned, bijectFromUnsigned;
147 import nxt.variant_ex : WordVariant;
148 import nxt.container.traits : mustAddGCRange;
149 import std.experimental.allocator.common : isAllocator;
150 import nxt.container.dynamic_array : Array = DynamicArray;
151 import nxt.construction : dupShallow;
152 
153 // version = enterSingleInfiniteMemoryLeakTest;
154 // version = nxt_benchmark;
155 // version = show;
156 // version = useMemoryErrorHandler;
157 // version = showBranchSizes;
158 // version = showAssertTags;
159 
160 alias isFixedTrieableKeyType = isIntegralBijectableType;
161 
162 /** Returns: `true` if `T` is a scalar trie key-type, `false` otherwise. */
163 enum isScalarTrieableKeyType(T) = (isFixedTrieableKeyType!T ||
164 								   (isInputRange!T &&
165 									isFixedTrieableKeyType!(ElementType!T)));
166 
167 /** Returns: `true` if `T` is a type that can be stored as a key in a trie, ` false` otherwise. */
168 template isTrieableKeyType(T)
169 {
170 	static if (is(T == struct))
171 	{
172 		import std.meta : allSatisfy;
173 		enum isTrieableKeyType = allSatisfy!(isScalarTrieableKeyType, // recurse
174 											 typeof(T.tupleof));
175 	}
176 	else static if (is(T == class))
177 		static assert("Class types cannot be stored in a radix tree because classes in D has reference semantics");
178 	else
179 		enum isTrieableKeyType = isScalarTrieableKeyType!T;
180 }
181 
182 version (useMemoryErrorHandler) unittest {
183 	import etc.linux.memoryerror : registerMemoryErrorHandler;
184 	registerMemoryErrorHandler();
185 	dbg("registerMemoryErrorHandler done");
186 }
187 
188 pure nothrow @safe @nogc unittest
189 { version (showAssertTags) dbg();
190 	static assert(isTrieableKeyType!(const(char)[]));
191 
192 	struct S { int x, y, z; double w; bool b; }
193 	static assert(isTrieableKeyType!(S));
194 }
195 
196 /** Binary power of radix, typically either 1, 2, 4 or 8. */
197 private enum span = 8;
198 /** Branch-multiplicity. Also called order. */
199 private enum radix = 2^^span;
200 
201 static assert(span == 8, "Radix is currently limited to 8");
202 static assert(size_t.sizeof == 8, "Currently requires a 64-bit CPU (size_t.sizeof == 8)");
203 
204 version (unittest)
205 {
206 	version = useModulo;
207 }
208 version = useModulo;			/+ TODO: remove and activate only in version (unittest) +/
209 
210 /** Radix Modulo Index
211 	Restricted index type avoids range checking in array indexing below.
212 */
213 version (useModulo)
214 {
215 	import nxt.modulo : Mod, mod;   /+ TODO: remove these if radix is `256` +/
216 	alias Ix = Mod!(radix, ubyte);
217 	alias UIx = Mod!(radix, size_t); // `size_t` is faster than `uint` on Intel Haswell
218 
219 	/** Mutable RawTree Key. */
220 	alias Key(size_t span) = Mod!(2^^span)[]; /+ TODO: use static_bitarray to more naturally support span != 8. +/
221 	/** Immutable RawTree Key. */
222 	alias IKey(size_t span) = immutable(Mod!(2^^span))[]; /+ TODO: use static_bitarray to more naturally support span != 8. +/
223 	/** Fixed-Length RawTree Key. */
224 	alias KeyN(size_t span, size_t N) = Mod!(2^^span)[N];
225 
226 	enum useModuloFlag = true;
227 }
228 else
229 {
230 	alias Ix = ubyte;
231 	alias UIx = size_t;
232 
233 	/** Mutable RawTree Key. */
234 	alias Key(size_t span) = ubyte[]; /+ TODO: use static_bitarray to more naturally support span != 8. +/
235 	/** Immutable RawTree Key. */
236 	alias IKey(size_t span) = immutable(ubyte)[]; /+ TODO: use static_bitarray to more naturally support span != 8. +/
237 	/** Fixed-Length RawTree Key. */
238 	alias KeyN(size_t span, size_t N) = ubyte[N];
239 
240 	enum useModuloFlag = false;
241 }
242 
243 import nxt.container.static_modarray : StaticModArray;
244 
245 import std.experimental.allocator.mallocator : Mallocator;
246 alias Ixs = Array!(Ix, Mallocator); // @safe to use `Mallocator` when elements aren't accessed by reference.
247 
248 alias IxsN(uint capacity,
249 		   uint elementLength) = StaticModArray!(capacity,
250 												 elementLength,
251 												 8,
252 												 useModuloFlag);
253 
254 /* TODO: what follow requires memory: */
255 
256 alias UKey = Key!span;
257 bool empty(UKey ukey) @safe pure nothrow
258 	=> ukey.length == 0;
259 
260 private template IxElt(Value)
261 {
262 	static if (is(Value == void)) // set case
263 		alias IxElt = UIx;
264 	else						// map case
265 		struct IxElt { UIx ix; Value value; }
266 }
267 
268 private static auto eltIx(Value)(inout IxElt!Value elt)
269 {
270 	static if (is(Value == void)) // set case
271 		return elt;
272 	else						// map case
273 		return elt.ix;
274 }
275 
276 private template Elt(Value)
277 {
278 	static if (is(Value == void)) // set case
279 		alias Elt = UKey;
280 	else						// map case
281 		struct Elt { UKey key; Value value; }
282 }
283 
284 private auto eltKey(Value)(inout Elt!Value elt)
285 {
286 	static if (is(Value == void)) // set case
287 		return elt;
288 	else						// map case
289 		return elt.key;
290 }
291 
292 private auto eltKeyDropExactly(Value)(Elt!Value elt, size_t n)
293 {
294 	static if (is(Value == void)) // set case
295 		return elt[n .. $];
296 	else						// map case
297 		return Elt!Value(elt.key[n .. $], elt.value);
298 }
299 
300 /** Results of attempt at modification sub. */
301 enum ModStatus:ubyte
302 {
303 	maxCapacityReached,		 // no operation, max capacity reached
304 	unchanged,				  // no operation, element already stored
305 	added, inserted = added,	// new element was added (inserted)
306 	updated, modified = updated, // existing element was update/modified
307 }
308 
309 /** Size of a CPU cache line in bytes.
310  *
311  * Container layouts should be adapted to make use of at least this many bytes
312  * in its nodes.
313 */
314 private enum cacheLineSize = 64;
315 
316 shared static this()
317 {
318 	import core.cpuid : dataCaches;
319 	assert(cacheLineSize == dataCaches()[0].lineSize, "Cache line is not 64 bytes");
320 }
321 
322 enum keySeparator = ',';
323 
324 /** Single/1-Key Leaf with maximum key-length 7. */
325 struct OneLeafMax7
326 {
327 	@safe pure:
328 	enum capacity = 7;
329 
330 	this(Ix[] key) nothrow @nogc
331 	{
332 		assert(key.length != 0);
333 		this.key = key;
334 	}
335 
336 	bool contains(UKey key) const nothrow @nogc
337 	{
338 		pragma(inline, true);
339 		return this.key == key;
340 	}
341 
342 	@property string toString()() const /* tlm to avoid  */
343 	{
344 		import std.string : format;
345 		string s;
346 		foreach (immutable i, immutable ix; key)
347 		{
348 			immutable first = i == 0; // first iteration
349 			if (!first) { s ~= '_'; }
350 			/+ TODO: avoid calling format here and make toString non-tlm +/
351 			s ~= format("%.2X", ix); // in hexadecimal
352 		}
353 		return s;
354 	}
355 
356 	IxsN!(capacity, 1) key;
357 }
358 
359 /** Binary/2-Key Leaf with key-length 3. */
360 struct TwoLeaf3
361 {
362 	enum keyLength = 3; // fixed length key
363 	enum capacity = 2; // maximum number of keys stored
364 
365 	pure nothrow @safe @nogc:
366 
367 	this(Keys...)(Keys keys)
368 	if (Keys.length != 0 &&
369 		Keys.length <= capacity)
370 	{
371 		this.keys = keys;
372 	}
373 
374 	inout(Ix)[] prefix() inout in(!keys.empty)
375 	{
376 		final switch (keys.length)
377 		{
378 		case 1:
379 			return keys.at!0[];
380 		case 2:
381 			import std.algorithm.searching : commonPrefix;
382 			return commonPrefix(keys.at!0[], keys.at!1[]);
383 		}
384 	}
385 
386 	bool contains(UKey key) const in(!keys.empty)
387 	{
388 		version (LDC) pragma(inline, true);
389 		final switch (keys.length)
390 		{
391 		case 1: return keys[0] == key;
392 		case 2: return keys[0] == key || keys[1] == key;
393 		}
394 		// return keys.contains(key);
395 	}
396 
397 	IxsN!(capacity, keyLength) keys; // should never be empty
398 }
399 
400 /** Ternary/3-Key Leaf with key-length 2. */
401 struct TriLeaf2
402 {
403 	enum keyLength = 2; // fixed length key
404 	enum capacity = 3; // maximum number of keys stored
405 
406 	pure nothrow @safe @nogc:
407 
408 	this(Keys...)(Keys keys)
409 	if (Keys.length != 0 &&
410 		Keys.length <= capacity)
411 	{
412 		this.keys = keys;
413 	}
414 
415 	inout(Ix)[] prefix() inout in(!keys.empty)
416 	{
417 		import std.algorithm.searching : commonPrefix;
418 		final switch (keys.length)
419 		{
420 		case 1:
421 			return keys.at!0[];
422 		case 2:
423 			return commonPrefix(keys.at!0[], keys.at!1[]);
424 		case 3:
425 			return commonPrefix(keys.at!0[],
426 								commonPrefix(keys.at!1[],
427 											 keys.at!2[])); /+ TODO: make and reuse variadic commonPrefix +/
428 		}
429 	}
430 
431 	bool contains(UKey key) const in(!keys.empty)
432 	{
433 		version (LDC) pragma(inline, true);
434 		final switch (keys.length)
435 		{
436 		case 1: return keys[0] == key;
437 		case 2: return keys[0] == key || keys[1] == key;
438 		case 3: return keys[0] == key || keys[1] == key || keys[2] == key;
439 		}
440 		// return keys.contains(key);
441 	}
442 
443 	IxsN!(capacity, keyLength) keys; // should never be empty
444 }
445 
446 /** Hepa/7-Key Leaf with key-length 1. */
447 struct HeptLeaf1
448 {
449 	enum keyLength = 1;
450 	enum capacity = 7; // maximum number of elements
451 
452 	pure nothrow @safe @nogc:
453 
454 	this(Keys...)(Keys keys)
455 	if (Keys.length != 0 &&
456 		Keys.length <= capacity)
457 	{
458 		pragma(inline, true);
459 		// static foreach (const ix, const key; keys)
460 		// {
461 		//	 this.keys[ix] = cast(Ix)key;
462 		// }
463 		this.keys = keys;
464 	}
465 
466 	bool contains(UIx key) const in(!keys.empty)
467 	{
468 		pragma(inline, true);
469 		// NOTE this is not noticably faster
470 		// final switch (keys.length)
471 		// {
472 		// case 1: return keys[0] == key;
473 		// case 2: return keys[0] == key || keys[1] == key;
474 		// case 3: return keys[0] == key || keys[1] == key || keys[2] == key;
475 		// case 4: return keys[0] == key || keys[1] == key || keys[2] == key || keys[3] == key;
476 		// case 5: return keys[0] == key || keys[1] == key || keys[2] == key || keys[3] == key || keys[4] == key;
477 		// case 6: return keys[0] == key || keys[1] == key || keys[2] == key || keys[3] == key || keys[4] == key || keys[5] == key;
478 		// case 7: return keys[0] == key || keys[1] == key || keys[2] == key || keys[3] == key || keys[4] == key || keys[5] == key || keys[6] == key;
479 		// }
480 		return keys.contains(key);
481 	}
482 	bool contains(UKey key) const
483 	{
484 		pragma(inline, true);
485 		return (key.length == 1 &&
486 				keys.contains(UIx(key[0])));
487 	}
488 
489 	IxsN!(capacity, 1) keys;	// should never be empty
490 }
491 
492 /** Check if type `NodeType` is a variable-length aggregate (`struct`) type. */
493 private enum hasVariableSize(NodeType) = __traits(hasMember, NodeType, "allocationSizeOfCapacity");
494 
495 /** Construct a `Node`-type of value type `NodeType` using constructor arguments
496  * `args` of `Args`.
497  */
498 auto makeFixedSizeNodeValue(NodeType, Args...)(Args args) pure nothrow
499 if (!hasVariableSize!NodeType &&
500 	!isPointer!NodeType)
501 {
502 	return NodeType(args);
503 }
504 
505 /** Allocate and Construct a `Node`-type of value type `NodeType` using
506  * constructor arguments `args` of `Args`.
507  */
508 auto makeFixedSizeNodePointer(NodeType, A, Args...)(auto ref A alloc, Args args) @trusted pure nothrow
509 if (isAllocator!A &&
510 	!hasVariableSize!NodeType &&
511 	!isPointer!NodeType)
512 {
513 	import std.experimental.allocator : make;
514 	// debug ++_heapAllocBalance;
515 	return make!(NodeType)(alloc, args);
516 	/+ TODO: ensure alignment of node at least that of NodeType.alignof +/
517 }
518 
519 /** Allocate (via `alloc.allocate`) and `emplace` an instance of a
520  * variable-length aggregate (`struct`) type `NodeType`.
521  */
522 private NodeType* makeVariableSizeNodePointer(NodeType, A, Args...)(auto ref A alloc, size_t requestedCapacity, Args args) @trusted
523 if (isAllocator!A &&
524 	is(NodeType == struct) &&
525 	hasVariableSize!NodeType)
526 {
527 	import std.math.algebraic : nextPow2;
528 	import std.algorithm.comparison : clamp;
529 	const paddedCapacity = (requestedCapacity == 1 ? 1 :
530 							nextPow2(requestedCapacity - 1).clamp(NodeType.minCapacity, // TODO what happens when requestedCapacity doesn’t fit in NodeType.maxCapacity?
531 																  NodeType.maxCapacity));
532 	// import nxt.debugio;
533 	// dbg(NodeType.stringof, " paddedCapacity:", paddedCapacity, " requestedCapacity:", requestedCapacity);
534 	// assert(paddedCapacity >= requestedCapacity); /+ TODO: this fails for dmd but not for ldc +/
535 	import core.lifetime : emplace;
536 	// TODO extend `std.experimental.allocator.make` to `makeWithCapacity`
537 	return emplace(cast(typeof(return))alloc.allocate(NodeType.allocationSizeOfCapacity(paddedCapacity)),
538 				   paddedCapacity, args);
539 }
540 
541 void freeNode(NodeType, A)(auto ref A alloc, NodeType nt) @trusted pure nothrow
542 if (isAllocator!A)
543 {
544 	static if (isPointer!NodeType)
545 	{
546 		import std.experimental.allocator : dispose;
547 		dispose(alloc, nt);
548 		// debug --_heapAllocBalance;
549 	}
550 }
551 
552 /** Sparsely coded leaves with values of type `Value`. */
553 static private struct SparseLeaf1(Value)
554 {
555 	import nxt.searching_ex : containsStoreIndex;
556 	import std.range : assumeSorted;
557 	import std.algorithm.sorting : isSorted;
558 
559 	enum hasValue = !is(Value == void);
560 
561 	enum minCapacity = 0;	 // preferred minimum number of preallocated values
562 
563 	// preferred maximum number of preallocated values, if larger use a DenseLeaf1 instead
564 	static if (hasValue)
565 		enum maxCapacity = 128;
566 	else
567 		enum maxCapacity = 48;
568 
569 	version (useModulo)
570 		alias Capacity = Mod!(maxCapacity + 1);
571 	else
572 		alias Capacity = ubyte;
573 
574 	alias Length = Capacity;
575 
576 	pure nothrow:
577 
578 	this(size_t capacity) @nogc
579 	{
580 		_capacity = cast(Capacity)capacity;
581 		_length = 0;
582 	}
583 
584 	/** Returns a an allocated duplicate of this.
585 	 *
586 	 * Shallowly duplicates the values in the map case.
587 	 */
588 	typeof(this)* dupAt(A)(auto ref A alloc)
589 	{
590 		static if (hasValue)
591 			return makeVariableSizeNodePointer!(typeof(this))(alloc, this._capacity, ixs, values);
592 		else
593 			return makeVariableSizeNodePointer!(typeof(this))(alloc, this._capacity, ixs);
594 	}
595 
596 	static if (hasValue)
597 	{
598 		static if (mustAddGCRange!Value)
599 			import core.memory : GC;
600 
601 		/** Construct with capacity `capacity`. */
602 		this(in size_t capacity, Ix[] ixs, Value[] values)
603 		in(ixs.length == values.length)
604 		in(capacity >= ixs.length)
605 		in(values.length <= maxCapacity)
606 		in(ixs.isSorted)
607 		{
608 			_capacity = capacity;
609 			_length = ixs.length;
610 			foreach (immutable i, immutable ix; ixs)
611 				ixsSlots[i] = ix;
612 			foreach (immutable i, immutable value; values)
613 				valuesSlots[i] = value;
614 			static if (mustAddGCRange!Value)
615 				GC.addRange(valuesSlots.ptr, _capacity * Value.sizeof);
616 		}
617 	}
618 	else
619 	{
620 		this(in size_t capacity, Ix[] ixs) @nogc
621 		in(capacity >= ixs.length)
622 		in(ixs.length <= maxCapacity)
623 		in(ixs.isSorted)
624 		{
625 			_capacity = cast(Capacity)capacity;
626 			_length = cast(Length)ixs.length;
627 			foreach (immutable i, immutable ix; ixs)
628 				ixsSlots[i] = ix;
629 		}
630 	}
631 
632 	~this() nothrow @nogc
633 	{
634 		deinit();
635 	}
636 
637 	private void deinit() @trusted nothrow @nogc
638 	{
639 		static if (hasValue && mustAddGCRange!Value)
640 			GC.removeRange(valuesSlots.ptr, _capacity * Value.sizeof);
641 	}
642 
643 	typeof(this)* makeRoom(A)(auto ref A alloc)
644 	{
645 		auto next = &this;
646 		if (full)
647 		{
648 			if (length < maxCapacity) // if we can expand more
649 			{
650 				// make room
651 				static if (hasValue)
652 					next = makeVariableSizeNodePointer!(typeof(this))(alloc, length + 1, ixsSlots, valuesSlots);
653 				else
654 					next = makeVariableSizeNodePointer!(typeof(this))(alloc, length + 1, ixsSlots);
655 				freeNode(alloc, &this);
656 			}
657 			else
658 				return null;	// indicate that max capacity has been reached
659 		}
660 		return next;
661 	}
662 
663 	/** Insert `key`, possibly self-reallocating `this` (into return). */
664 	typeof(this)* reconstructingInsert(A)(auto ref A alloc, IxElt!Value elt, out ModStatus modStatus, out size_t index) @trusted
665 	{
666 		// get index
667 		static if (hasValue)
668 			immutable ix = elt.ix;
669 		else
670 			immutable ix = elt;
671 
672 		// handle existing element
673 		if (ixs.assumeSorted.containsStoreIndex(ix, index))
674 		{
675 			static if (hasValue)
676 			{
677 				modStatus = valuesSlots[index] != elt.value ? ModStatus.updated : ModStatus.unchanged;
678 				valuesSlots[index] = elt.value;
679 			}
680 			else
681 				modStatus = ModStatus.unchanged;
682 			return &this;
683 		}
684 
685 		// try making room for new element
686 		typeof(return) next = makeRoom(alloc);
687 		if (next is null)
688 		{
689 			modStatus = ModStatus.maxCapacityReached; /+ TODO: expand to `DenseLeaf1` +/
690 			return &this;
691 		}
692 
693 		// insert new element
694 		next.insertAt(index, elt);
695 		modStatus = ModStatus.added;
696 
697 		return next;
698 	}
699 
700 	private void insertAt(size_t index, IxElt!Value elt) in(index <= _length)
701 	{
702 		foreach (immutable i; 0 .. _length - index) /+ TODO: functionize this loop or reuse memmove: +/
703 		{
704 			immutable iD = _length - i;
705 			immutable iS = iD - 1;
706 			ixsSlots[iD] = ixsSlots[iS];
707 		}
708 		static if (hasValue)
709 		{
710 			ixsSlots[index] = elt.ix;
711 			valuesSlots[index] = elt.value;
712 		}
713 		else
714 		{
715 			ixsSlots[index] = cast(Ix)elt;
716 		}
717 		++_length;
718 	}
719 
720 	pragma(inline, true) Length length() const @safe @nogc => _length;
721 	pragma(inline, true) Capacity capacity() const @safe @nogc => _capacity;
722 
723 	pragma(inline, true) bool empty() const @property @safe @nogc => _length == 0;
724 	pragma(inline, true) bool full() const @safe @nogc => _length == _capacity;
725 
726 	/** Get all initialized keys. */
727 	pragma(inline, true) auto ixs() inout @trusted @nogc => ixsSlots[0 .. _length];
728 
729 	static if (hasValue)
730 	{
731 		/** Get all intialized values. */
732 		inout(Value)[] values() inout @trusted @nogc
733 		{
734 			pragma(inline, true)
735 			return valuesSlots[0 .. _length];
736 		}
737 
738 		void setValue(UIx ix, Value value) @trusted
739 		{
740 			size_t index;
741 			immutable hit = ixs.assumeSorted.containsStoreIndex(ix, index);
742 			assert(hit);		// assert hit for now
743 			assert(index < length);
744 			values[index] = value;
745 		}
746 
747 		inout(Value*) contains(UIx key) inout @nogc
748 		{
749 			pragma(inline, true);
750 			size_t index;
751 			if (ixs.assumeSorted.containsStoreIndex(key, index))
752 				return &(values[index]);
753 			else
754 				return null;
755 		}
756 	}
757 	else
758 	{
759 		pragma(inline, true)
760 		bool contains(UIx key) const @nogc => ixs.assumeSorted.contains(key);
761 	}
762 
763 	/** Get all reserved keys. */
764 	private auto ixsSlots() inout @trusted @nogc
765 	{
766 		pragma(inline, true);
767 		static if (hasValue)
768 			return (cast(Ix*)(_values.ptr + _capacity))[0 .. _capacity];
769 		else
770 			return _ixs.ptr[0 .. _capacity];
771 	}
772 	static if (hasValue)
773 	{
774 		/** Get all reserved values. */
775 		pragma(inline, true)
776 		private inout(Value)[] valuesSlots() inout @trusted @nogc => _values.ptr[0 .. _capacity];
777 	}
778 
779 	/** Get allocation size (in bytes) needed to hold `length` number of
780 	 * elements (keys and optionally values).
781 	 */
782 	static size_t allocationSizeOfCapacity(in size_t capacity) pure nothrow @safe @nogc
783 	{
784 		static if (hasValue)
785 			return (this.sizeof + // base plus
786 					Value.sizeof*capacity + // actual size of `_values`
787 					Ix.sizeof*capacity);   // actual size of `_ixs`
788 		else
789 			return (this.sizeof + // base plus
790 					Ix.sizeof*capacity);   // actual size of `_ixs`
791 	}
792 
793 	/** Get allocated size (in bytes) of `this` including the variable-length part.
794 	 */
795 	size_t allocatedSize() const pure nothrow @safe @nogc
796 		=> allocationSizeOfCapacity(_capacity);
797 
798 private:
799 	Length _length;
800 	const Capacity _capacity;
801 	static if (hasValue)
802 		Value[0] _values;
803 	Ix[0] _ixs;
804 }
805 
806 /** Densely coded leaves with values of type `Value`. */
807 static private struct DenseLeaf1(Value)
808 {
809 	import nxt.container.static_bitarray : StaticBitArray;
810 
811 	enum hasValue = !is(Value == void);
812 
813 	static if (hasValue)
814 		enum hasGCScannedValues = !is(Value == bool) && mustAddGCRange!Value;
815 	else
816 		enum hasGCScannedValues = false;
817 
818 	static if (hasGCScannedValues)
819 		import core.memory : GC;
820 
821 	enum capacity = radix;
822 
823 	@safe pure nothrow:
824 
825 	static if (hasValue)
826 	{
827 		this(Ix[] ixs, Value[] values) @nogc
828 		{
829 			assert(ixs.length <= capacity);
830 			assert(ixs.length == values.length);
831 			foreach (immutable i, immutable ix; ixs)
832 			{
833 				_ixBits[ix] = true;
834 				_values[ix] = values[i];
835 			}
836 			static if (hasGCScannedValues)
837 				GC.addRange(_values.ptr, capacity * Value.size);
838 		}
839 
840 		this(ref StaticBitArray!capacity ixBits, Value[] values) @nogc
841 		{
842 			assert(ixBits.length == values.length);
843 			_ixBits = ixBits;
844 			_values[] = values[]; /+ TODO: commenting out does not affect unittests +/
845 			static if (hasGCScannedValues)
846 				GC.addRange(_values.ptr, capacity * Value.size);
847 		}
848 	}
849 	else
850 	{
851 		this(Ix[] ixs) @nogc
852 		{
853 			assert(ixs.length <= capacity);
854 			foreach (immutable ix; ixs)
855 				_ixBits[ix] = true;
856 		}
857 
858 		this(const ref StaticBitArray!capacity ixBits) @nogc
859 		{
860 			_ixBits = ixBits;
861 		}
862 	}
863 
864 
865 	/** Returns a an allocated duplicate of this.
866 		Shallowly duplicates the values in the map case.
867 	*/
868 	typeof(this)* dupAt(A)(auto ref A alloc)
869 	{
870 		static if (hasValue)
871 			return makeFixedSizeNodePointer!(typeof(this))(alloc, _ixBits, _values);
872 		else
873 			return makeFixedSizeNodePointer!(typeof(this))(alloc, _ixBits);
874 	}
875 
876 	~this() nothrow @nogc
877 	{
878 		static if (hasGCScannedValues)
879 			GC.removeRange(_values.ptr, capacity * Value.size);
880 	}
881 
882 	pragma(inline, true) bool empty() const @property @nogc => _ixBits.allZero;
883 	pragma(inline, true) bool full() const @nogc => _ixBits.allOne;
884 	pragma(inline, true) size_t count() const @nogc => _ixBits.countOnes;
885 
886 	static if (hasValue)
887 		inout(Value*) contains(UIx ix) inout @nogc
888 		{
889 			pragma(inline, true);
890 			return _ixBits[ix] ? &(_values[ix]) : null;
891 		}
892 	else
893 		bool contains(UIx ix) const @nogc
894 		{
895 			pragma(inline, true);
896 			return _ixBits[ix];
897 		}
898 
899 	ModStatus insert(IxElt!Value elt)
900 	{
901 		ModStatus modStatus;
902 
903 		static if (hasValue)
904 			immutable ix = elt.ix;
905 		else
906 			immutable ix = elt;
907 
908 		if (contains(ix))
909 		{
910 			static if (hasValue)
911 				modStatus = _values[ix] != elt.value ? ModStatus.updated : ModStatus.unchanged;
912 			else
913 				modStatus = ModStatus.unchanged;
914 		}
915 		else
916 		{
917 			_ixBits[ix] = true;
918 			modStatus = ModStatus.added;
919 		}
920 
921 		// set element
922 		static if (hasValue)
923 			_values[ix] = elt.value;
924 
925 		return modStatus;
926 	}
927 
928 	/** Try to find index to first set bit in `_ixBits` starting at bit index `ix` and put the result in `nextIx`.
929 		Returns: `true` upon find, `false` otherwise.
930 		TODO: move to StaticBitArray
931 	 */
932 	bool tryFindSetBitIx(UIx ix, out UIx nextIx) const @nogc
933 	{
934 		pragma(inline, true);
935 		assert(!_ixBits.allZero);
936 		return _ixBits.canFindIndexOf(true, ix, nextIx);
937 	}
938 
939 	bool tryFindNextSetBitIx(UIx ix, out UIx nextIx) const @nogc
940 	{
941 		pragma(inline, true);
942 		immutable ix1 = cast(uint)(ix + 1);
943 		return ix1 != radix && tryFindSetBitIx(UIx(ix1), nextIx);
944 	}
945 
946 	static if (hasValue)
947 	{
948 		/** Set value at index `ix` to `value`. */
949 		void setValue(UIx ix, Value value) @nogc
950 		{
951 			pragma(inline, true);
952 			_values[ix] = value;
953 		}
954 
955 		inout(Value)[capacity] values() inout @nogc
956 		{
957 			version (D_Coverage) {} else version (LDC) pragma(inline, true);
958 			return _values;
959 		}
960 	}
961 
962 private:
963 	StaticBitArray!capacity _ixBits;  // 32 bytes
964 	static if (hasValue)
965 	{
966 		// static if (is(Value == bool))
967 		// {
968 		//	 StaticBitArray!capacity _values; // packed values
969 		// }
970 		// else
971 		// {
972 		//	 Value[capacity] _values;
973 		// }
974 		Value[capacity] _values;
975 	}
976 }
977 
978 /** Fixed-Length leaf Key-only Node. */
979 alias FixedKeyLeafN = WordVariant!(OneLeafMax7,
980 								   TwoLeaf3,
981 								   TriLeaf2);
982 
983 /** Mutable leaf node of 1-Ix leaves.
984  *
985  * Used by branch-leaf.
986  */
987 alias Leaf1(Value) = WordVariant!(HeptLeaf1, /+ TODO: remove from case when Value is void +/
988 								  SparseLeaf1!Value*,
989 								  DenseLeaf1!Value*);
990 
991 /** Returns: `true` if `key` is stored under `curr`, `false` otherwise. */
992 UIx firstIx(Value)(Leaf1!Value curr)
993 {
994 	final switch (curr.typeIx) with (Leaf1!Value.Ix)
995 	{
996 	case undefined:
997 		assert(false);
998 	case ix_HeptLeaf1:
999 	case ix_SparseLeaf1Ptr:
1000 		return 0;		   // always first
1001 	case ix_DenseLeaf1Ptr:
1002 		auto curr_ = curr.as!(DenseLeaf1!Value*);
1003 		UIx nextIx;
1004 		immutable bool hit = curr_.tryFindSetBitIx(0, nextIx);
1005 		assert(hit);
1006 		return nextIx;
1007 	}
1008 }
1009 
1010 /** Try to iterate leaf index `ix` to index, either sparse or dense and put result in `nextIx`.
1011  *
1012  * Returns: `true` if `nextIx` was set, `false` if no more indexes was available.
1013  */
1014 pragma(inline) bool tryNextIx(Value)(Leaf1!Value curr, const UIx ix, out Ix nextIx)
1015 {
1016 	final switch (curr.typeIx) with (Leaf1!Value.Ix)
1017 	{
1018 	case undefined:
1019 		assert(false);
1020 	case ix_HeptLeaf1:
1021 		immutable curr_ = curr.as!(HeptLeaf1);
1022 		if (ix + 1 == curr_.keys.length)
1023 		{
1024 			return false;
1025 		}
1026 		else
1027 		{
1028 			nextIx = Ix(ix + 1);
1029 			return true;
1030 		}
1031 	case ix_SparseLeaf1Ptr:
1032 		immutable curr_ = curr.as!(SparseLeaf1!Value*);
1033 		if (ix + 1 == curr_.length)
1034 		{
1035 			return false;
1036 		}
1037 		else
1038 		{
1039 			nextIx = Ix(ix + 1);
1040 			return true;
1041 		}
1042 	case ix_DenseLeaf1Ptr:
1043 		return curr.as!(DenseLeaf1!Value*).tryFindNextSetBitIx(ix, nextIx);
1044 	}
1045 }
1046 
1047 static assert((DenseLeaf1!void).sizeof == 32);
1048 
1049 /** Adaptive radix tree/trie (ART) container storing untyped (raw) keys.
1050 
1051 	Params:
1052 		 Value = value type.
1053 		 A_ = memory allocator for bin array and large bins (`LargeBin`)
1054 
1055 	In set-case (`Value` is `void`) this container contains specific memory
1056 	optimizations for representing a set pointers or integral types (of fixed
1057 	length).
1058 
1059 	Radix-trees are suitable for storing ordered sets/maps with variable-length
1060 	keys and provide completion of all its keys matching a given
1061 	key-prefix. This enables, for instance, efficient storage and retrieval of
1062 	large sets of long URLs with high probability of sharing a common prefix,
1063 	typically a domain and path.
1064 
1065 	Branch packing of leaves is more efficient when `Key.sizeof` is fixed, that
1066 	is, the member `hasFixedKeyLength` returns `true`.
1067 
1068 	For optimal performance, the individual bit-chunks should be arranged
1069 	starting with most sparse chunks first. For integers this means most
1070 	significant chunk (byte) first. This includes IEEE-compliant floating point
1071 	numbers.
1072 
1073 	For a good introduction to adaptive radix trees (ART) see $(HTTP infosys.cs.uni-saarland.de/publications/ARCD15.pdf)
1074 
1075 	See_Also: $(HTTP www.ietf.org/rfc/rfc2616.txt, RFC2616)
1076 
1077 	See_Also: $(HTTP en.wikipedia.org/wiki/Trie)
1078 	See_Also: $(HTTP en.wikipedia.org/wiki/Radix_tree)
1079 	See_Also: $(HTTP github.com/npgall/concurrent-trees)
1080 	See_Also: $(HTTP code.dogmap.org/kart/)
1081 	See_Also: $(HTTP cr.yp.to/critbit.html)
1082 	See_Also: $(HTTP gcc.gnu.org/onlinedocs/libstdc++/ext/pb_ds/trie_based_containers.html)
1083 	See_Also: $(HTTP github.com/npgall/concurrent-trees)
1084 */
1085 template RawRadixTree(Value = void, A_) if (isAllocator!A_) // TODO rename A_ something better later
1086 {
1087 	import std.algorithm.mutation : move;
1088 	import std.algorithm.comparison : min, max;
1089 
1090 	enum isValue = !is(Value == void);
1091 
1092 	version (useModulo)
1093 		alias SubCount = Mod!(radix + 1);
1094 	else
1095 		alias SubCount = uint; // needed because for inclusive range 0 .. 256
1096 
1097 	struct IxSub { UIx ix; Node node; }
1098 
1099 	/** Sparse/Packed dynamically sized branch implemented as variable-length
1100 		struct.
1101 	*/
1102 	static private struct SparseBranch
1103 	{
1104 		enum minCapacity = 0; // minimum number of preallocated sub-indexes and sub-nodes
1105 		enum maxCapacity = 48; // maximum number of preallocated sub-indexes and sub-nodes
1106 		enum prefixCapacity = 5; // 5, 13, 21, ...
1107 
1108 		version (useModulo)
1109 			alias Count = Mod!(maxCapacity + 1);
1110 		else
1111 			alias Count = ubyte;
1112 
1113 		@safe pure nothrow:
1114 
1115 		this(size_t subCapacity)
1116 		{
1117 			initialize(subCapacity);
1118 		}
1119 
1120 		this(size_t subCapacity, const Ix[] prefix, Leaf1!Value leaf1)
1121 		{
1122 			initialize(subCapacity);
1123 			this.prefix = prefix;
1124 			this.leaf1 = leaf1;
1125 		}
1126 
1127 		this(size_t subCapacity, const Ix[] prefix)
1128 		{
1129 			initialize(subCapacity);
1130 			this.prefix = prefix;
1131 		}
1132 
1133 		this(size_t subCapacity, Leaf1!Value leaf1)
1134 		{
1135 			initialize(subCapacity);
1136 			this.leaf1 = leaf1;
1137 		}
1138 
1139 		this(size_t subCapacity, const Ix[] prefix, IxSub sub)
1140 		{
1141 			assert(subCapacity);
1142 
1143 			initialize(subCapacity);
1144 
1145 			this.prefix = prefix;
1146 			this.subCount = 1;
1147 			this.subIxSlots[0] = sub.ix;
1148 			this.subNodeSlots[0] = sub.node;
1149 		}
1150 
1151 		this(size_t subCapacity, typeof(this)* rhs)
1152 		in
1153 		{
1154 			assert(subCapacity > rhs.subCapacity);
1155 			assert(rhs);
1156 		}
1157 		do
1158 		{
1159 			// these two must be in this order:
1160 			// 1.
1161 			move(rhs.leaf1, this.leaf1);
1162 			debug rhs.leaf1 = typeof(rhs.leaf1).init;
1163 			this.subCount = rhs.subCount;
1164 			move(rhs.prefix, this.prefix);
1165 
1166 			// 2.
1167 			initialize(subCapacity);
1168 
1169 			// copy variable length part. TODO: optimize:
1170 			this.subIxSlots[0 .. rhs.subCount] = rhs.subIxSlots[0 .. rhs.subCount];
1171 			this.subNodeSlots[0 .. rhs.subCount] = rhs.subNodeSlots[0 .. rhs.subCount];
1172 
1173 			assert(this.subCapacity > rhs.subCapacity);
1174 		}
1175 
1176 		private void initialize(size_t subCapacity)
1177 		{
1178 			// assert(subCapacity != 4);
1179 			this.subCapacity = cast(Count)subCapacity;
1180 			debug		   // only zero-initialize in debug mode
1181 			{
1182 				// zero-initialize variable-length part
1183 				subIxSlots[] = Ix.init;
1184 				subNodeSlots[] = Node.init;
1185 			}
1186 		}
1187 
1188 		typeof(this)* dupAt(A)(auto ref A alloc)
1189 		{
1190 			typeof(this)* copy = makeVariableSizeNodePointer!(typeof(this))(alloc, this.subCapacity);
1191 			copy.leaf1 = treedup(this.leaf1, alloc);
1192 			copy.prefix = this.prefix;
1193 			copy.subCount = this.subCount;
1194 
1195 			copy.subIxSlots[0 .. subCount] = this.subIxSlots[0 .. subCount]; // copy initialized
1196 			debug copy.subIxSlots[subCount .. $] = Ix.init;				  // zero rest in debug
1197 
1198 			foreach (immutable i; 0 .. subCount)
1199 				copy.subNodeSlots[i] = treedup(subNodeSlots[i], alloc);
1200 			return copy;
1201 		}
1202 
1203 		~this() nothrow @nogc
1204 		{
1205 			deinit();
1206 		}
1207 
1208 		private void deinit() @nogc { /* nothing for now */ }
1209 
1210 		/** Insert `sub`, possibly self-reallocating `this` (into return).
1211 		 */
1212 		typeof(this)* reconstructingInsert(A)(auto ref A alloc, IxSub sub,
1213 										   out ModStatus modStatus,
1214 										   out size_t index) @trusted
1215 		{
1216 			typeof(this)* next = &this;
1217 
1218 			import nxt.searching_ex : containsStoreIndex;
1219 			if (subIxs.containsStoreIndex(sub.ix, index))
1220 			{
1221 				assert(subIxSlots[index] == sub.ix); // subIxSlots[index] = sub[0];
1222 				subNodeSlots[index] = sub.node;
1223 				modStatus = ModStatus.updated;
1224 				return next;
1225 			}
1226 
1227 			if (full)
1228 			{
1229 				if (subCount < maxCapacity) // if we can expand more
1230 				{
1231 					next = makeVariableSizeNodePointer!(typeof(this))(alloc, subCount + 1, &this);
1232 					freeNode(alloc, &this);
1233 				}
1234 				else
1235 				{
1236 					modStatus = ModStatus.maxCapacityReached; /+ TODO: expand to `DenseBranch` +/
1237 					return next;
1238 				}
1239 			}
1240 
1241 			next.insertAt(index, sub);
1242 			modStatus = ModStatus.added;
1243 			return next;
1244 		}
1245 
1246 		private void insertAt(size_t index, IxSub sub)
1247 		{
1248 			assert(index <= subCount);
1249 			foreach (immutable i; 0 .. subCount - index) /+ TODO: functionize this loop or reuse memmove: +/
1250 			{
1251 				immutable iD = subCount - i;
1252 				immutable iS = iD - 1;
1253 				subIxSlots[iD] = subIxSlots[iS];
1254 				subNodeSlots[iD] = subNodeSlots[iS];
1255 			}
1256 			subIxSlots[index] = cast(Ix)sub.ix; // set new element
1257 			subNodeSlots[index] = sub.node; // set new element
1258 			++subCount;
1259 		}
1260 
1261 		inout(Node) subNodeAt(UIx ix) inout
1262 		{
1263 			import nxt.searching_ex : binarySearch; // need this instead of `SortedRange.contains` because we need the index
1264 			immutable hitIndex = subIxSlots[0 .. subCount].binarySearch(ix); // find index where insertion should be made
1265 			return (hitIndex != typeof(hitIndex).max) ? subNodeSlots[hitIndex] : Node.init;
1266 		}
1267 
1268 		pragma(inline, true) bool empty() const @property @nogc => subCount == 0;
1269 		pragma(inline, true) bool full()  const @nogc => subCount == subCapacity;
1270 
1271 		import std.range : assumeSorted;
1272 
1273 		pragma(inline, true) auto subIxs() inout @nogc => subIxSlots[0 .. subCount].assumeSorted;
1274 		pragma(inline, true) inout(Node)[] subNodes() inout @nogc => subNodeSlots[0 .. subCount];
1275 		pragma(inline, true) inout(Node) firstSubNode() inout @nogc => subCount ? subNodeSlots[0] : typeof(return).init;
1276 
1277 		/** Get all sub-`Ix` slots, both initialized and uninitialized. */
1278 		private auto subIxSlots() inout @trusted pure nothrow // TODO use `inout(Ix)[]` return type
1279 			=> (cast(Ix*)(_subNodeSlots0.ptr + subCapacity))[0 .. subCapacity];
1280 
1281 		/** Get all sub-`Node` slots, both initialized and uninitialized. */
1282 		private inout(Node)[] subNodeSlots() inout @trusted pure nothrow
1283 			=> _subNodeSlots0.ptr[0 .. subCapacity];
1284 
1285 		/** Append statistics of tree under `this` into `stats`. */
1286 		void appendStatsTo(ref Stats stats) const
1287 		{
1288 			size_t count = 0; // number of non-zero sub-nodes
1289 			foreach (const Node sub; subNodes)
1290 			{
1291 				++count;
1292 				sub.appendStatsTo!(Value, A_)(stats);
1293 			}
1294 			assert(count <= radix);
1295 			++stats.popHist_SparseBranch[count]; /+ TODO: type-safe indexing +/
1296 
1297 			stats.sparseBranchAllocatedSizeSum += allocatedSize;
1298 
1299 			if (leaf1)
1300 				leaf1.appendStatsTo!(Value, A_)(stats);
1301 		}
1302 
1303 		/** Get allocation size (in bytes) needed to hold `length` number of
1304 			sub-indexes and sub-nodes. */
1305 		static size_t allocationSizeOfCapacity(in size_t capacity) pure nothrow @safe @nogc
1306 			=> (this.sizeof + // base plus
1307 				Node.sizeof*capacity + // actual size of `_subNodeSlots0`
1308 				Ix.sizeof*capacity);   // actual size of `_subIxSlots0`
1309 
1310 		/** Get allocated size (in bytes) of `this` including the variable-length part. */
1311 		size_t allocatedSize() const pure nothrow @safe @nogc
1312 			=> allocationSizeOfCapacity(subCapacity);
1313 
1314 	private:
1315 
1316 		// members in order of decreasing `alignof`:
1317 		Leaf1!Value leaf1;
1318 
1319 		IxsN!(prefixCapacity, 1) prefix; // prefix common to all `subNodes` (also called edge-label)
1320 		Count subCount;
1321 		Count subCapacity;
1322 		static assert(prefix.sizeof + subCount.sizeof + subCapacity.sizeof == 8); // assert alignment
1323 
1324 		// variable-length part
1325 		Node[0] _subNodeSlots0;
1326 		Ix[0] _subIxSlots0;	 // needs to special alignment
1327 	}
1328 
1329 	static assert(hasVariableSize!SparseBranch);
1330 	static if (!isValue)
1331 		static assert(SparseBranch.sizeof == 16);
1332 
1333 	/** Dense/Unpacked `radix`-branch with `radix` number of sub-nodes. */
1334 	static private struct DenseBranch
1335 	{
1336 		enum maxCapacity = 256;
1337 		enum prefixCapacity = 15; // 7, 15, 23, ..., we can afford larger prefix here because DenseBranch is so large
1338 
1339 		@safe pure nothrow:
1340 
1341 		this(const Ix[] prefix)
1342 		{
1343 			this.prefix = prefix;
1344 		}
1345 
1346 		this(const Ix[] prefix, IxSub sub)
1347 		{
1348 			this(prefix);
1349 			this.subNodes[sub.ix] = sub.node;
1350 		}
1351 
1352 		this(const Ix[] prefix, IxSub subA, IxSub subB)
1353 		{
1354 			assert(subA.ix != subB.ix); // disjunct indexes
1355 			assert(subA.node != subB.node); // disjunct nodes
1356 
1357 			this.subNodes[subA.ix] = subA.node;
1358 			this.subNodes[subB.ix] = subB.node;
1359 		}
1360 
1361 		this(SparseBranch* rhs)
1362 		{
1363 			this.prefix = rhs.prefix;
1364 
1365 			// move leaf
1366 			move(rhs.leaf1, this.leaf1);
1367 			debug rhs.leaf1 = Leaf1!Value.init; // make reference unique, to be on the safe side
1368 
1369 			foreach (immutable i; 0 .. rhs.subCount) // each sub node. TODO: use iota!(Mod!N)
1370 			{
1371 				immutable iN = (cast(ubyte)i).mod!(SparseBranch.maxCapacity);
1372 				immutable subIx = UIx(rhs.subIxSlots[iN]);
1373 
1374 				this.subNodes[subIx] = rhs.subNodeSlots[iN];
1375 				debug rhs.subNodeSlots[iN] = null; // make reference unique, to be on the safe side
1376 			}
1377 		}
1378 
1379 		typeof(this)* dupAt(A)(auto ref A alloc) @trusted /+ TODO: remove @trusted qualifier when .ptr problem has been fixed +/
1380 		{
1381 			auto copy = makeFixedSizeNodePointer!(typeof(this))(alloc);
1382 			copy.leaf1 = treedup(leaf1, alloc);
1383 			copy.prefix = prefix;
1384 			foreach (immutable i, subNode; subNodes)
1385 				copy.subNodes.ptr[i] = treedup(subNode, alloc); /+ TODO: remove .ptr access when I inout problem is solved +/
1386 			return copy;
1387 		}
1388 
1389 		/** Number of non-null sub-Nodes. */
1390 		SubCount subCount() const
1391 		{
1392 			typeof(return) count = 0; // number of non-zero sub-nodes
1393 			foreach (immutable subNode; subNodes) /+ TODO: why can't we use std.algorithm.count here? +/
1394 				if (subNode)
1395 					++count;
1396 			assert(count <= radix);
1397 			return count;
1398 		}
1399 
1400 		bool findSubNodeAtIx(size_t currIx, out UIx nextIx) inout @nogc
1401 		{
1402 			import std.algorithm.searching : countUntil;
1403 			immutable cnt = subNodes[currIx .. $].countUntil!(subNode => cast(bool)subNode);
1404 			immutable bool hit = cnt >= 0;
1405 			if (hit)
1406 			{
1407 				const nextIx_ = currIx + cnt;
1408 				assert(nextIx_ <= Ix.max);
1409 				nextIx = cast(Ix)nextIx_;
1410 			}
1411 			return hit;
1412 		}
1413 
1414 		/** Append statistics of tree under `this` into `stats`. */
1415 		void appendStatsTo(ref Stats stats) const
1416 		{
1417 			size_t count = 0; // number of non-zero sub-nodes
1418 			foreach (const Node subNode; subNodes)
1419 				if (subNode)
1420 				{
1421 					++count;
1422 					subNode.appendStatsTo!(Value, A_)(stats);
1423 				}
1424 			assert(count <= radix);
1425 			++stats.popHist_DenseBranch[count]; /+ TODO: type-safe indexing +/
1426 			if (leaf1)
1427 				leaf1.appendStatsTo!(Value, A_)(stats);
1428 		}
1429 
1430 	private:
1431 		// members in order of decreasing `alignof`:
1432 		Leaf1!Value leaf1;
1433 		IxsN!(prefixCapacity, 1) prefix; // prefix (edge-label) common to all `subNodes`
1434 		import nxt.typecons_ex : IndexedBy;
1435 		IndexedBy!(Node[radix], UIx) subNodes;
1436 	}
1437 
1438 	version (showBranchSizes)
1439 	{
1440 		pragma(msg, "SparseBranch.sizeof:", SparseBranch.sizeof, " SparseBranch.alignof:", SparseBranch.alignof);
1441 		pragma(msg, "DenseBranch.sizeof:", DenseBranch.sizeof, " DenseBranch.alignof:", DenseBranch.alignof);
1442 
1443 		pragma(msg, "SparseBranch.prefix.sizeof:", SparseBranch.prefix.sizeof, " SparseBranch.prefix.alignof:", SparseBranch.prefix.alignof);
1444 		pragma(msg, "DenseBranch.prefix.sizeof:", DenseBranch.prefix.sizeof, " DenseBranch.prefix.alignof:", DenseBranch.prefix.alignof);
1445 
1446 		// pragma(msg, "SparseBranch.subNodes.sizeof:", SparseBranch.subNodes.sizeof, " SparseBranch.subNodes.alignof:", SparseBranch.subNodes.alignof);
1447 		// pragma(msg, "SparseBranch.subIxs.sizeof:", SparseBranch.subIxs.sizeof, " SparseBranch.subIxs.alignof:", SparseBranch.subIxs.alignof);
1448 	}
1449 
1450 	/+ TODO: make these run-time arguments at different key depths and map to statistics of typed-key +/
1451 	alias DefaultBranch = SparseBranch; // either `SparseBranch`, `DenseBranch`
1452 
1453 	/** Mutable node. */
1454 	alias Node = WordVariant!(OneLeafMax7,
1455 							  TwoLeaf3,
1456 							  TriLeaf2,
1457 
1458 							  HeptLeaf1,
1459 							  SparseLeaf1!Value*,
1460 							  DenseLeaf1!Value*,
1461 
1462 							  SparseBranch*,
1463 							  DenseBranch*);
1464 
1465 	/** Mutable branch node. */
1466 	alias Branch = WordVariant!(SparseBranch*,
1467 								DenseBranch*);
1468 
1469 	static assert(Node.typeBits <= IxsN!(7, 1).typeBits);
1470 	static assert(Leaf1!Value.typeBits <= IxsN!(7, 1).typeBits);
1471 	static assert(Branch.typeBits <= IxsN!(7, 1).typeBits);
1472 
1473 	/** Constant node. */
1474 	/+ TODO: make work with indexNaming +/
1475 	// import std.typecons : ConstOf;
1476 	// alias ConstNodePtr = WordVariant!(staticMap!(ConstOf, Node));
1477 
1478 	static assert(span <= 8*Ix.sizeof, "Need more precision in Ix");
1479 
1480 	/** Element reference. */
1481 	private static struct ElementRef
1482 	{
1483 		Node node;
1484 		UIx ix; // `Node`-specific counter, typically either a sparse or dense index either a sub-branch or a `UKey`-ending `Ix`
1485 		ModStatus modStatus;
1486 
1487 		pragma(inline, true)
1488 		bool opCast(T : bool)() const pure nothrow @safe @nogc => cast(bool)node;
1489 	}
1490 
1491 	/** Branch Range (Iterator). */
1492 	private static struct BranchRange
1493 	{
1494 		@safe pure nothrow:
1495 
1496 		this(SparseBranch* branch)
1497 		{
1498 			this.branch = Branch(branch);
1499 
1500 			this._subsEmpty = branch.subCount == 0;
1501 			this._subCounter = 0; // always zero
1502 
1503 			if (branch.leaf1)
1504 				this.leaf1Range = Leaf1Range(branch.leaf1);
1505 
1506 			cacheFront();
1507 		}
1508 
1509 		this(DenseBranch* branch)
1510 		{
1511 			this.branch = Branch(branch);
1512 
1513 			this._subCounter = 0; /+ TODO: needed? +/
1514 			_subsEmpty = !branch.findSubNodeAtIx(0, this._subCounter);
1515 
1516 			if (branch.leaf1)
1517 				this.leaf1Range = Leaf1Range(branch.leaf1);
1518 
1519 			cacheFront();
1520 		}
1521 
1522 		pragma(inline, true) UIx frontIx() const @nogc in(!empty) => _cachedFrontIx;
1523 		pragma(inline, true) bool atLeaf1() const @nogc in(!empty) => _frontAtLeaf1;
1524 
1525 		private UIx subFrontIx() const
1526 		{
1527 			final switch (branch.typeIx) with (Branch.Ix)
1528 			{
1529 			case undefined:
1530 				assert(false);
1531 			case ix_SparseBranchPtr:
1532 				return UIx(branch.as!(SparseBranch*).subIxs[_subCounter]);
1533 			case ix_DenseBranchPtr:
1534 				return _subCounter;
1535 			}
1536 		}
1537 
1538 		private Node subFrontNode() const
1539 		{
1540 			final switch (branch.typeIx) with (Branch.Ix)
1541 			{
1542 			case undefined:
1543 				assert(false);
1544 			case ix_SparseBranchPtr: return branch.as!(SparseBranch*).subNodeSlots[_subCounter];
1545 			case ix_DenseBranchPtr: return branch.as!(DenseBranch*).subNodes[_subCounter];
1546 			}
1547 		}
1548 
1549 		void appendFrontIxsToKey(ref Ixs key) const @nogc
1550 		{
1551 			final switch (branch.typeIx) with (Branch.Ix)
1552 			{
1553 			case undefined:
1554 				assert(false);
1555 			case ix_SparseBranchPtr:
1556 				key ~= branch.as!(SparseBranch*).prefix[];
1557 				break;
1558 			case ix_DenseBranchPtr:
1559 				key ~= branch.as!(DenseBranch*).prefix[];
1560 				break;
1561 			}
1562 			key ~= cast(Ix)frontIx; // uses cached data so ok to not depend on branch type
1563 		}
1564 
1565 		size_t prefixLength() const @nogc
1566 		{
1567 			final switch (branch.typeIx) with (Branch.Ix)
1568 			{
1569 			case undefined:
1570 				assert(false);
1571 			case ix_SparseBranchPtr: return branch.as!(SparseBranch*).prefix.length;
1572 			case ix_DenseBranchPtr: return  branch.as!(DenseBranch*).prefix.length;
1573 			}
1574 		}
1575 
1576 		pragma(inline, true) bool empty() const @property @nogc => leaf1Range.empty && _subsEmpty;
1577 		pragma(inline, true) bool subsEmpty() const @nogc => _subsEmpty;
1578 
1579 		/** Try to iterated forward.
1580 			Returns: `true` upon successful forward iteration, `false` otherwise (upon completion of iteration),
1581 		*/
1582 		void popFront() in(!empty)
1583 		{
1584 			// pop front element
1585 			if (_subsEmpty)
1586 				leaf1Range.popFront();
1587 			else if (leaf1Range.empty)
1588 				popBranchFront();
1589 			else				// both non-empty
1590 			{
1591 				if (leaf1Range.front <= subFrontIx) // `a` before `ab`
1592 					leaf1Range.popFront();
1593 				else
1594 					popBranchFront();
1595 			}
1596 			if (!empty) { cacheFront(); }
1597 		}
1598 
1599 		/** Fill cached value and remember if we we next element is direct
1600 			(_frontAtLeaf1 is true) or under a sub-branch (_frontAtLeaf1 is
1601 			false).
1602 		*/
1603 		void cacheFront() in(!empty)
1604 		{
1605 			if (_subsEmpty)	 // no more sub-nodes
1606 			{
1607 				_cachedFrontIx = leaf1Range.front;
1608 				_frontAtLeaf1 = true;
1609 			}
1610 			else if (leaf1Range.empty) // no more in direct leaf node
1611 			{
1612 				_cachedFrontIx = subFrontIx;
1613 				_frontAtLeaf1 = false;
1614 			}
1615 			else				// both non-empty
1616 			{
1617 				immutable leaf1Front = leaf1Range.front;
1618 				if (leaf1Front <= subFrontIx) // `a` before `ab`
1619 				{
1620 					_cachedFrontIx = leaf1Front;
1621 					_frontAtLeaf1 = true;
1622 				}
1623 				else
1624 				{
1625 					_cachedFrontIx = subFrontIx;
1626 					_frontAtLeaf1 = false;
1627 				}
1628 			}
1629 		}
1630 
1631 		private void popBranchFront()
1632 		{
1633 			/+ TODO: move all calls to Branch-specific members popFront() +/
1634 			final switch (branch.typeIx) with (Branch.Ix)
1635 			{
1636 			case undefined:
1637 				assert(false);
1638 			case ix_SparseBranchPtr:
1639 				auto branch_ = branch.as!(SparseBranch*);
1640 				if (_subCounter + 1 == branch_.subCount)
1641 					_subsEmpty = true;
1642 				else
1643 					++_subCounter;
1644 				break;
1645 			case ix_DenseBranchPtr:
1646 				auto branch_ = branch.as!(DenseBranch*);
1647 				_subsEmpty = !branch_.findSubNodeAtIx(_subCounter + 1, this._subCounter);
1648 				break;
1649 			}
1650 		}
1651 
1652 	private:
1653 		Branch branch;		  // branch part of range
1654 		Leaf1Range leaf1Range;  // range of direct leaves
1655 
1656 		UIx _subCounter; // `Branch`-specific counter, typically either a sparse or dense index either a sub-branch or a `UKey`-ending `Ix`
1657 		UIx _cachedFrontIx;
1658 
1659 		bool _frontAtLeaf1;   // `true` iff front is currently at a leaf1 element
1660 		bool _subsEmpty;	  // `true` iff no sub-nodes exists
1661 	}
1662 
1663 	/** Leaf1 Range (Iterator). */
1664 	private static struct Leaf1Range
1665 	{
1666 		this(Leaf1!Value leaf1)
1667 		{
1668 			this.leaf1 = leaf1;
1669 			bool emptied;
1670 			this._ix = firstIx(emptied);
1671 			if (emptied)
1672 				this.leaf1 = null;
1673 		}
1674 
1675 		@safe pure nothrow:
1676 
1677 		/** Get first index in current subkey. */
1678 		UIx front() const in(!empty)
1679 		{
1680 			final switch (leaf1.typeIx) with (Leaf1!Value.Ix)
1681 			{
1682 			case undefined:
1683 				assert(false);
1684 			case ix_HeptLeaf1:
1685 				static if (isValue)
1686 					assert(false, "HeptLeaf1 cannot store a value");
1687 				else
1688 					return UIx(leaf1.as!(HeptLeaf1).keys[_ix]);
1689 			case ix_SparseLeaf1Ptr:
1690 				return UIx(leaf1.as!(SparseLeaf1!Value*).ixs[_ix]);
1691 			case ix_DenseLeaf1Ptr:
1692 				return _ix;
1693 			}
1694 		}
1695 
1696 		static if (isValue)
1697 		{
1698 			Value frontValue() const in(!empty)
1699 			{
1700 				final switch (leaf1.typeIx) with (Leaf1!Value.Ix)
1701 				{
1702 				case undefined:
1703 					assert(false);
1704 				case ix_HeptLeaf1: assert(false, "HeptLeaf1 cannot store a value");
1705 				case ix_SparseLeaf1Ptr:
1706 					return leaf1.as!(SparseLeaf1!Value*).values[_ix];
1707 				case ix_DenseLeaf1Ptr:
1708 					return leaf1.as!(DenseLeaf1!Value*).values[_ix];
1709 				}
1710 			}
1711 		}
1712 
1713 		bool empty() const @property @nogc => leaf1.isNull;
1714 
1715 		/** Pop front element.
1716 		 */
1717 		void popFront() in(!empty)
1718 		{
1719 			/+ TODO: move all calls to leaf1-specific members popFront() +/
1720 			final switch (leaf1.typeIx) with (Leaf1!Value.Ix)
1721 			{
1722 			case undefined:
1723 				assert(false);
1724 			case ix_HeptLeaf1:
1725 				static if (isValue)
1726 					assert(false, "HeptLeaf1 cannot store a value");
1727 				else
1728 				{
1729 					immutable leaf_ = leaf1.as!(HeptLeaf1);
1730 					if (_ix + 1 == leaf_.keys.length)
1731 						leaf1 = null;
1732 					else
1733 						++_ix;
1734 					break;
1735 				}
1736 			case ix_SparseLeaf1Ptr:
1737 				const leaf_ = leaf1.as!(SparseLeaf1!Value*);
1738 				if (_ix + 1 == leaf_.length)
1739 					leaf1 = null;
1740 				else
1741 					++_ix;
1742 				break;
1743 			case ix_DenseLeaf1Ptr:
1744 				const leaf_ = leaf1.as!(DenseLeaf1!Value*);
1745 				if (!leaf_.tryFindNextSetBitIx(_ix, _ix))
1746 					leaf1 = null;
1747 				break;
1748 			}
1749 		}
1750 
1751 		static if (isValue)
1752 		{
1753 			auto ref value() inout
1754 			{
1755 				final switch (leaf1.typeIx) with (Leaf1!Value.Ix)
1756 				{
1757 				case undefined:
1758 					assert(false);
1759 				case ix_HeptLeaf1: assert(false, "HeptLeaf1 cannot store a value");
1760 				case ix_SparseLeaf1Ptr: return leaf1.as!(SparseLeaf1!Value*).values[_ix];
1761 				case ix_DenseLeaf1Ptr: return leaf1.as!(DenseLeaf1!Value*).values[_ix];
1762 				}
1763 			}
1764 		}
1765 
1766 		private UIx firstIx(out bool empty) const
1767 		{
1768 			final switch (leaf1.typeIx) with (Leaf1!Value.Ix)
1769 			{
1770 			case undefined:
1771 				assert(false);
1772 			case ix_HeptLeaf1:
1773 				static if (isValue)
1774 					assert(false, "HeptLeaf1 cannot store a value");
1775 				else
1776 					return UIx(0);		   // always first
1777 			case ix_SparseLeaf1Ptr:
1778 				auto leaf_ = leaf1.as!(SparseLeaf1!Value*);
1779 				empty = leaf_.empty;
1780 				return UIx(0);		   // always first
1781 			case ix_DenseLeaf1Ptr:
1782 				auto leaf_ = leaf1.as!(DenseLeaf1!Value*);
1783 				UIx nextIx;
1784 				immutable bool hit = leaf_.tryFindSetBitIx(UIx(0), nextIx);
1785 				assert(hit);
1786 				return nextIx;
1787 			}
1788 		}
1789 
1790 	private:
1791 		Leaf1!Value leaf1; /+ TODO: Use Leaf1!Value-WordVariant when it includes non-Value leaf1 types +/
1792 		UIx _ix; // `Node`-specific counter, typically either a sparse or dense index either a sub-branch or a `UKey`-ending `Ix`
1793 	}
1794 
1795 	/** Leaf Value Range (Iterator). */
1796 	private static struct LeafNRange
1797 	{
1798 		this(Node leaf)
1799 		{
1800 			this.leaf = leaf;
1801 			bool emptied;
1802 			this.ix = firstIx(emptied);
1803 			if (emptied)
1804 				this.leaf = null;
1805 		}
1806 
1807 		@safe pure nothrow:
1808 
1809 		private UIx firstIx(out bool emptied) const
1810 		{
1811 			switch (leaf.typeIx) with (Node.Ix)
1812 			{
1813 			case undefined:
1814 				assert(false);
1815 			case ix_OneLeafMax7:
1816 			case ix_TwoLeaf3:
1817 			case ix_TriLeaf2:
1818 			case ix_HeptLeaf1:
1819 				return UIx(0);		   // always first
1820 			case ix_SparseLeaf1Ptr:
1821 				const leaf_ = leaf.as!(SparseLeaf1!Value*);
1822 				emptied = leaf_.empty;
1823 				return UIx(0);		   // always first
1824 			case ix_DenseLeaf1Ptr:
1825 				const leaf_ = leaf.as!(DenseLeaf1!Value*);
1826 				UIx nextIx;
1827 				immutable bool hit = leaf_.tryFindSetBitIx(UIx(0), nextIx);
1828 				assert(hit);
1829 				return nextIx;
1830 			default: assert(false, "Unsupported Node type");
1831 			}
1832 		}
1833 
1834 		/** Get first index in current subkey. */
1835 		UIx frontIx() in(!empty)
1836 		{
1837 			switch (leaf.typeIx) with (Node.Ix)
1838 			{
1839 			case undefined:
1840 				assert(false);
1841 			case ix_OneLeafMax7:
1842 				assert(ix == 0);
1843 				return UIx(leaf.as!(OneLeafMax7).key[0]);
1844 			case ix_TwoLeaf3:
1845 				return UIx(leaf.as!(TwoLeaf3).keys[ix][0]);
1846 			case ix_TriLeaf2:
1847 				return UIx(leaf.as!(TriLeaf2).keys[ix][0]);
1848 			case ix_HeptLeaf1:
1849 				return UIx(leaf.as!(HeptLeaf1).keys[ix]);
1850 			case ix_SparseLeaf1Ptr:
1851 				return UIx(leaf.as!(SparseLeaf1!Value*).ixs[ix]);
1852 			case ix_DenseLeaf1Ptr:
1853 				return ix;
1854 			default: assert(false, "Unsupported Node type");
1855 			}
1856 		}
1857 
1858 		void appendFrontIxsToKey(ref Ixs key) const @nogc in(!empty)
1859 		{
1860 			switch (leaf.typeIx) with (Node.Ix)
1861 			{
1862 			case undefined:
1863 				assert(false);
1864 			case ix_OneLeafMax7:
1865 				assert(ix == 0);
1866 				key ~= leaf.as!(OneLeafMax7).key[];
1867 				break;
1868 			case ix_TwoLeaf3:
1869 				key ~= leaf.as!(TwoLeaf3).keys[ix][];
1870 				break;
1871 			case ix_TriLeaf2:
1872 				key ~= leaf.as!(TriLeaf2).keys[ix][];
1873 				break;
1874 			case ix_HeptLeaf1:
1875 				key ~= Ix(leaf.as!(HeptLeaf1).keys[ix]);
1876 				break;
1877 			case ix_SparseLeaf1Ptr:
1878 				key ~= Ix(leaf.as!(SparseLeaf1!Value*).ixs[ix]);
1879 				break;
1880 			case ix_DenseLeaf1Ptr:
1881 				key ~= cast(Ix)ix;
1882 				break;
1883 			default: assert(false, "Unsupported Node type");
1884 			}
1885 		}
1886 
1887 		pragma(inline, true) bool empty() const @property @nogc => !leaf;
1888 		pragma(inline, true) void makeEmpty() @nogc { leaf = null; }
1889 
1890 		/** Pop front element. */
1891 		void popFront() in(!empty)
1892 		{
1893 			/+ TODO: move all calls to leaf-specific members popFront() +/
1894 			switch (leaf.typeIx) with (Node.Ix)
1895 			{
1896 			case undefined:
1897 				assert(false);
1898 			case ix_OneLeafMax7:
1899 				makeEmpty(); // only one element so forward automatically completes
1900 				break;
1901 			case ix_TwoLeaf3:
1902 				auto leaf_ = leaf.as!(TwoLeaf3);
1903 				if (ix + 1 == leaf_.keys.length) { makeEmpty(); } else { ++ix; }
1904 				break;
1905 			case ix_TriLeaf2:
1906 				auto leaf_ = leaf.as!(TriLeaf2);
1907 				if (ix + 1 == leaf_.keys.length) { makeEmpty(); } else { ++ix; }
1908 				break;
1909 			case ix_HeptLeaf1:
1910 				auto leaf_ = leaf.as!(HeptLeaf1);
1911 				if (ix + 1 == leaf_.keys.length) { makeEmpty(); } else { ++ix; }
1912 				break;
1913 			case ix_SparseLeaf1Ptr:
1914 				auto leaf_ = leaf.as!(SparseLeaf1!Value*);
1915 				if (ix + 1 == leaf_.length) { makeEmpty(); } else { ++ix; }
1916 				break;
1917 			case ix_DenseLeaf1Ptr:
1918 				auto leaf_ = leaf.as!(DenseLeaf1!Value*);
1919 				immutable bool emptied = !leaf_.tryFindNextSetBitIx(ix, ix);
1920 				if (emptied)
1921 					makeEmpty();
1922 				break;
1923 			default: assert(false, "Unsupported Node type");
1924 			}
1925 		}
1926 
1927 		static if (isValue)
1928 		{
1929 			auto ref value() inout
1930 			{
1931 				switch (leaf.typeIx) with (Node.Ix)
1932 				{
1933 				case ix_SparseLeaf1Ptr: return leaf.as!(SparseLeaf1!Value*).values[ix];
1934 				case ix_DenseLeaf1Ptr: return leaf.as!(DenseLeaf1!Value*).values[ix];
1935 				default: assert(false, "Incorrect Leaf-type");
1936 				}
1937 			}
1938 		}
1939 
1940 	private:
1941 		Node leaf;			  /+ TODO: Use Leaf-WordVariant when it includes non-Value leaf types +/
1942 		UIx ix; // `Node`-specific counter, typically either a sparse or dense index either a sub-branch or a `UKey`-ending `Ix`
1943 	}
1944 
1945 	/** Ordered Set of BranchRanges. */
1946 	private static struct BranchRanges
1947 	{
1948 		static if (isValue)
1949 		{
1950 			bool appendFrontIxsToKey(ref Ixs key, ref Value value) const @trusted
1951 			{
1952 				foreach (const ref branchRange; _bRanges)
1953 				{
1954 					branchRange.appendFrontIxsToKey(key);
1955 					if (branchRange.atLeaf1)
1956 					{
1957 						value = branchRange.leaf1Range.frontValue;
1958 						return true; // key and value are both complete
1959 					}
1960 				}
1961 				return false;   // only key is partially complete
1962 			}
1963 		}
1964 		else
1965 		{
1966 			bool appendFrontIxsToKey(ref Ixs key) const @trusted
1967 			{
1968 				foreach (const ref branchRange; _bRanges)
1969 				{
1970 					branchRange.appendFrontIxsToKey(key);
1971 					if (branchRange.atLeaf1)
1972 						return true; // key is complete
1973 				}
1974 				return false;   // key is not complete
1975 			}
1976 		}
1977 		size_t get1DepthAt(size_t depth) const @trusted
1978 		{
1979 			foreach (immutable i, ref branchRange; _bRanges[depth .. $])
1980 			{
1981 				if (branchRange.atLeaf1)
1982 					return depth + i;
1983 			}
1984 			return typeof(_branch1Depth).max;
1985 		}
1986 
1987 		private void updateLeaf1AtDepth(size_t depth) @trusted
1988 		{
1989 			if (_bRanges[depth].atLeaf1)
1990 			{
1991 				if (_branch1Depth == typeof(_branch1Depth).max) // if not yet defined
1992 					_branch1Depth = min(depth, _branch1Depth);
1993 			}
1994 			assert(_branch1Depth == get1DepthAt(0));
1995 		}
1996 
1997 		pragma(inline, true)
1998 		size_t getNext1DepthAt() const => get1DepthAt(_branch1Depth + 1);
1999 
2000 		pragma(inline, true)
2001 		bool hasBranch1Front() const => _branch1Depth != typeof(_branch1Depth).max;
2002 
2003 		void popBranch1Front()
2004 		{
2005 			pragma(inline, true);
2006 			// _branchesKeyPrefix.popBackN(_bRanges.back);
2007 			_bRanges[_branch1Depth].popFront();
2008 		}
2009 
2010 		pragma(inline, true)
2011 		bool emptyBranch1() const => _bRanges[_branch1Depth].empty;
2012 
2013 		pragma(inline, true)
2014 		bool atLeaf1() const => _bRanges[_branch1Depth].atLeaf1;
2015 
2016 		void shrinkTo(size_t newLength)
2017 		{
2018 			pragma(inline, true);
2019 			// turn emptyness exception into an assert like ranges do
2020 			// size_t suffixLength = 0;
2021 			// foreach (const ref branchRange; _bRanges[$ - newLength .. $]) /+ TODO: reverse isearch +/
2022 			// {
2023 			//	 suffixLength += branchRange.prefixLength + 1;
2024 			// }
2025 			// _branchesKeyPrefix.popBackN(suffixLength);
2026 			_bRanges.length = newLength;
2027 		}
2028 
2029 		void push(ref BranchRange branchRange)
2030 		{
2031 			pragma(inline, true);
2032 			// branchRange.appendFrontIxsToKey(_branchesKeyPrefix);
2033 			_bRanges ~= branchRange;
2034 		}
2035 
2036 		pragma(inline, true)
2037 		size_t branchCount() const pure nothrow @safe @nogc => _bRanges.length;
2038 
2039 		pragma(inline, true)
2040 		void pop() => _bRanges.popBack(); // _branchesKeyPrefix.popBackN(_bRanges.back.prefixLength + 1);
2041 
2042 		pragma(inline, true)
2043 		ref BranchRange bottom() => _bRanges.back;
2044 
2045 		pragma(inline, true)
2046 		private void verifyBranch1Depth() => assert(_branch1Depth == get1DepthAt(0));
2047 
2048 		void resetBranch1Depth()
2049 		{
2050 			pragma(inline, true);
2051 			_branch1Depth = typeof(_branch1Depth).max;
2052 		}
2053 
2054 		@property typeof(this) save()
2055 		{
2056 			typeof(this) copy;
2057 			copy._bRanges = this._bRanges.dupShallow; // ...this is inferred as @safe
2058 			copy._branch1Depth = this._branch1Depth;
2059 			return copy;
2060 		}
2061 
2062 	private:
2063 		import std.experimental.allocator.mallocator : Mallocator;
2064 		Array!(BranchRange, Mallocator) _bRanges;
2065 		// Ixs _branchesKeyPrefix;
2066 
2067 		// index to first branchrange in `_bRanges` that is currently on a leaf1
2068 		// or `typeof.max` if undefined
2069 		size_t _branch1Depth = size_t.max;
2070 	}
2071 
2072 	/** Forward (Left) Single-Directional Range over Tree. */
2073 	private static struct FrontRange
2074 	{
2075 		@safe pure nothrow:
2076 
2077 		this(Node root) @nogc
2078 		{
2079 			if (root)
2080 			{
2081 				diveAndVisitTreeUnder(root, 0);
2082 				cacheFront();
2083 			}
2084 		}
2085 
2086 		void popFront() @nogc
2087 		{
2088 			debug branchRanges.verifyBranch1Depth();
2089 
2090 			if (branchRanges.hasBranch1Front) // if we're currently at leaf1 of branch
2091 				popFrontInBranchLeaf1();
2092 			else				// if bottommost leaf should be popped
2093 			{
2094 				leafNRange.popFront();
2095 				if (leafNRange.empty)
2096 					postPopTreeUpdate();
2097 			}
2098 
2099 			if (!empty)
2100 				cacheFront();
2101 		}
2102 
2103 		private void popFrontInBranchLeaf1() @nogc /+ TODO: move to member of BranchRanges +/
2104 		{
2105 			branchRanges.popBranch1Front();
2106 			if (branchRanges.emptyBranch1)
2107 			{
2108 				branchRanges.shrinkTo(branchRanges._branch1Depth); // remove `branchRange` and all others below
2109 				branchRanges.resetBranch1Depth();
2110 				postPopTreeUpdate();
2111 			}
2112 			else if (!branchRanges.atLeaf1) // if not at leaf
2113 				branchRanges._branch1Depth = branchRanges.getNext1DepthAt;
2114 			else				// still at leaf
2115 			{
2116 				// nothing needed
2117 			}
2118 		}
2119 
2120 		private void cacheFront() @nogc
2121 		{
2122 			_cachedFrontKey.length = 0; // not clear() because we want to keep existing allocation
2123 
2124 			// branches
2125 			static if (isValue)
2126 			{
2127 				if (branchRanges.appendFrontIxsToKey(_cachedFrontKey,
2128 													 _cachedFrontValue))
2129 					return;
2130 			}
2131 			else
2132 			{
2133 				if (branchRanges.appendFrontIxsToKey(_cachedFrontKey))
2134 					return;
2135 			}
2136 
2137 			// leaf
2138 			if (!leafNRange.empty)
2139 			{
2140 				leafNRange.appendFrontIxsToKey(_cachedFrontKey);
2141 				static if (isValue)
2142 					_cachedFrontValue = leafNRange.value; // last should be leaf containing value
2143 			}
2144 		}
2145 
2146 		// Go upwards and iterate forward in parents.
2147 		private void postPopTreeUpdate() @nogc
2148 		{
2149 			while (branchRanges.branchCount)
2150 			{
2151 				branchRanges.bottom.popFront();
2152 				if (branchRanges.bottom.empty)
2153 					branchRanges.pop();
2154 				else			// if not empty
2155 				{
2156 					if (branchRanges.bottom.atLeaf1)
2157 						branchRanges._branch1Depth = min(branchRanges.branchCount - 1,
2158 														 branchRanges._branch1Depth);
2159 					break;	  // branchRanges.bottom is not empty so break
2160 				}
2161 			}
2162 			if (branchRanges.branchCount &&
2163 				!branchRanges.bottom.subsEmpty) // if any sub nodes
2164 				diveAndVisitTreeUnder(branchRanges.bottom.subFrontNode,
2165 									  branchRanges.branchCount); // visit them
2166 		}
2167 
2168 		/** Find ranges of branches and leaf for all nodes under tree `root`. */
2169 		private void diveAndVisitTreeUnder(Node root, size_t depth) @nogc
2170 		{
2171 			Node curr = root;
2172 			Node next;
2173 			do
2174 			{
2175 				final switch (curr.typeIx) with (Node.Ix)
2176 				{
2177 				case undefined:
2178 					assert(false);
2179 				case ix_OneLeafMax7:
2180 				case ix_TwoLeaf3:
2181 				case ix_TriLeaf2:
2182 				case ix_HeptLeaf1:
2183 				case ix_SparseLeaf1Ptr:
2184 				case ix_DenseLeaf1Ptr:
2185 					assert(leafNRange.empty);
2186 					leafNRange = LeafNRange(curr);
2187 					next = null; // we're done diving
2188 					break;
2189 				case ix_SparseBranchPtr:
2190 					auto curr_ = curr.as!(SparseBranch*);
2191 					auto currRange = BranchRange(curr_);
2192 					branchRanges.push(currRange);
2193 					branchRanges.updateLeaf1AtDepth(depth);
2194 					next = (curr_.subCount) ? curr_.firstSubNode : Node.init;
2195 					break;
2196 				case ix_DenseBranchPtr:
2197 					auto curr_ = curr.as!(DenseBranch*);
2198 					auto currRange = BranchRange(curr_);
2199 					branchRanges.push(currRange);
2200 					branchRanges.updateLeaf1AtDepth(depth);
2201 					next = branchRanges.bottom.subsEmpty ? Node.init : branchRanges.bottom.subFrontNode;
2202 					break;
2203 				}
2204 				curr = next;
2205 				++depth;
2206 			}
2207 			while (next);
2208 		}
2209 
2210 		@property typeof(this) save() @nogc @trusted /+ TODO: remove @trusted +/
2211 		{
2212 			typeof(this) copy;
2213 			copy.leafNRange = this.leafNRange;
2214 			copy.branchRanges = this.branchRanges.save;
2215 			copy._cachedFrontKey = this._cachedFrontKey.dupShallow;
2216 			static if (isValue)
2217 				copy._cachedFrontValue = this._cachedFrontValue;
2218 			return copy;
2219 		}
2220 
2221 		/** Check if empty. */
2222 		pragma(inline, true)
2223 		bool empty() const @property @nogc => (leafNRange.empty && branchRanges.branchCount == 0);
2224 
2225 		/** Get front key. */
2226 		pragma(inline, true)
2227 		auto frontKey() const @trusted @nogc => _cachedFrontKey[]; /+ TODO: replace @trusted with DIP-1000 scope +/
2228 
2229 		static if (isValue)
2230 		{
2231 			/** Get front value. */
2232 			pragma(inline, true)
2233 			auto frontValue() const @nogc => _cachedFrontValue;
2234 		}
2235 
2236 	private:
2237 		LeafNRange leafNRange;
2238 		BranchRanges branchRanges;
2239 
2240 		// cache
2241 		Ixs _cachedFrontKey; // copy of front key
2242 		static if (isValue)
2243 			Value _cachedFrontValue; // copy of front value
2244 	}
2245 
2246 	/** Bi-Directional Range over Tree.
2247 		Fulfills `isBidirectionalRange`.
2248 	*/
2249 	private static struct Range
2250 	{
2251 		import nxt.algorithm.searching : startsWith;
2252 
2253 		pure nothrow:
2254 
2255 		this(Node root, UKey keyPrefix) @nogc
2256 		{
2257 			this._rawKeyPrefix = keyPrefix;
2258 
2259 			this._front = FrontRange(root);
2260 			/+ TODO: this._back = FrontRange(root); +/
2261 
2262 			if (!empty &&
2263 				!_front.frontKey.startsWith(_rawKeyPrefix))
2264 				popFront();
2265 		}
2266 
2267 		@property typeof(this) save() @nogc
2268 		{
2269 			typeof(this) copy;
2270 			copy._front = this._front.save;
2271 			copy._back = this._back.save;
2272 			copy._rawKeyPrefix = this._rawKeyPrefix;
2273 			return copy;
2274 		}
2275 
2276 		pragma(inline, true)
2277 		bool empty() const @property @nogc => _front.empty; /+ TODO: _front == _back; +/
2278 
2279 		pragma(inline, true)
2280 		auto lowKey() const @nogc => _front.frontKey[_rawKeyPrefix.length .. $];
2281 
2282 		pragma(inline, true)
2283 		auto highKey() const @nogc => _back.frontKey[_rawKeyPrefix.length .. $];
2284 
2285 		void popFront() @nogc in(!empty)
2286 		{
2287 			do { _front.popFront(); }
2288 			while (!empty &&
2289 				   !_front.frontKey.startsWith(_rawKeyPrefix));
2290 		}
2291 
2292 		void popBack() @nogc in(!empty)
2293 		{
2294 			do { _back.popFront(); }
2295 			while (!empty &&
2296 				   !_back.frontKey.startsWith(_rawKeyPrefix));
2297 		}
2298 
2299 	private:
2300 		FrontRange _front;
2301 		FrontRange _back;
2302 		UKey _rawKeyPrefix;
2303 	}
2304 
2305 	/** Sparse-Branch population histogram. */
2306 	alias SparseLeaf1_PopHist = size_t[radix];
2307 
2308 	/** 256-Branch population histogram. */
2309 	alias DenseLeaf1_PopHist = size_t[radix];
2310 
2311 	/** 4-Branch population histogram.
2312 		Index maps to population with value range (0 .. 4).
2313 	*/
2314 	alias SparseBranch_PopHist = size_t[SparseBranch.maxCapacity + 1];
2315 
2316 	/** Radix-Branch population histogram.
2317 		Index maps to population with value range (0 .. `radix`).
2318 	*/
2319 	alias DenseBranch_PopHist = size_t[radix + 1];
2320 
2321 	/** Tree Population and Memory-Usage Statistics. */
2322 	struct Stats
2323 	{
2324 		SparseLeaf1_PopHist popHist_SparseLeaf1;
2325 		DenseLeaf1_PopHist popHist_DenseLeaf1;
2326 		SparseBranch_PopHist popHist_SparseBranch; // packed branch population histogram
2327 		DenseBranch_PopHist popHist_DenseBranch; // full branch population histogram
2328 
2329 		import nxt.typecons_ex : IndexedArray;
2330 
2331 		/** Maps `Node` type/index `Ix` to population.
2332 
2333 			Used to calculate complete tree memory usage, excluding allocator
2334 			overhead typically via `malloc` and `calloc`.
2335 		*/
2336 		IndexedArray!(size_t, Node.Ix) popByNodeType;
2337 		static assert(is(typeof(popByNodeType).Index == Node.Ix));
2338 
2339 		IndexedArray!(size_t, Leaf1!Value.Ix) popByLeaf1Type;
2340 		static assert(is(typeof(popByLeaf1Type).Index == Leaf1!Value.Ix));
2341 
2342 		/** Number of heap-allocated `Node`s. Should always equal `heapAllocationBalance`. */
2343 		size_t heapNodeCount;
2344 
2345 		/** Number of heap-allocated `Leaf`s. Should always equal `heapLeafAllocationBalance`. */
2346 		size_t heapLeafCount;
2347 
2348 		size_t sparseBranchAllocatedSizeSum;
2349 
2350 		size_t sparseLeaf1AllocatedSizeSum;
2351 	}
2352 
2353 	/** Destructively expand `curr` to a branch node able to store
2354 		`capacityIncrement` more sub-nodes.
2355 	*/
2356 	Branch expand(A)(auto ref A alloc, SparseBranch* curr, in size_t capacityIncrement = 1) @safe pure nothrow
2357 	in(curr.subCount < radix)	// we shouldn't expand beyond radix
2358 	{
2359 		typeof(return) next;
2360 		if (curr.empty)	 // if curr also empty length capacity must be zero
2361 			next = makeVariableSizeNodePointer!(typeof(*curr))(alloc, 1, curr); // so allocate one
2362 		else if (curr.subCount + capacityIncrement <= curr.maxCapacity) // if we can expand to curr
2363 		{
2364 			immutable requiredCapacity = curr.subCapacity + capacityIncrement;
2365 			auto next_ = makeVariableSizeNodePointer!(typeof(*curr))(alloc, requiredCapacity, curr);
2366 			assert(next_.subCapacity >= requiredCapacity);
2367 			next = next_;
2368 		}
2369 		else
2370 			next = makeFixedSizeNodePointer!(DenseBranch)(alloc, curr);
2371 		freeNode(alloc, curr);
2372 		return next;
2373 	}
2374 
2375 	/** Destructively expand `curr` to a leaf node able to store
2376 		`capacityIncrement` more sub-nodes.
2377 	*/
2378 	Leaf1!Value expand(A)(auto ref A alloc, SparseLeaf1!Value* curr, in size_t capacityIncrement = 1) @safe pure nothrow
2379 	in(curr.length < radix)		// we shouldn't expand beyond radix
2380 	{
2381 		typeof(return) next;
2382 		if (curr.empty)	 // if curr also empty length capacity must be zero
2383 			next = makeVariableSizeNodePointer!(typeof(*curr))(alloc, capacityIncrement); // make room for at least one
2384 		else if (curr.length + capacityIncrement <= curr.maxCapacity) // if we can expand to curr
2385 		{
2386 			immutable requiredCapacity = curr.capacity + capacityIncrement;
2387 			static if (isValue)
2388 				auto next_ = makeVariableSizeNodePointer!(typeof(*curr))(alloc, requiredCapacity, curr.ixs, curr.values);
2389 			else
2390 				auto next_ = makeVariableSizeNodePointer!(typeof(*curr))(alloc, requiredCapacity, curr.ixs);
2391 			assert(next_.capacity >= requiredCapacity);
2392 			next = next_;
2393 		}
2394 		else
2395 		{
2396 			static if (isValue)
2397 				next = makeFixedSizeNodePointer!(DenseLeaf1!Value)(alloc, curr.ixs, curr.values); /+ TODO: make use of sortedness of `curr.keys`? +/
2398 			else
2399 				next = makeFixedSizeNodePointer!(DenseLeaf1!Value)(alloc, curr.ixs); /+ TODO: make use of sortedness of `curr.keys`? +/
2400 		}
2401 		freeNode(alloc, curr);
2402 		return next;
2403 	}
2404 
2405 	/// ditto
2406 
2407 	Branch setSub(A)(auto ref A alloc, SparseBranch* curr, UIx subIx, Node subNode) @safe pure nothrow
2408 	{
2409 		// debug if (willFail) { dbg("WILL FAIL: subIx:", subIx); }
2410 		size_t insertionIndex;
2411 		ModStatus modStatus;
2412 		curr = curr.reconstructingInsert(alloc, IxSub(subIx, subNode), modStatus, insertionIndex);
2413 		if (modStatus == ModStatus.maxCapacityReached) // try insert and if it fails
2414 		{
2415 			// we need to expand because `curr` is full
2416 			auto next = expand(alloc, curr);
2417 			assert(getSub(next, subIx) == Node.init); // key slot should be unoccupied
2418 			return setSub(alloc, next, subIx, subNode);
2419 		}
2420 		return Branch(curr);
2421 	}
2422 
2423 	/// ditto
2424 	Branch setSub(DenseBranch* curr, UIx subIx, Node subNode) pure nothrow @safe @nogc
2425 	in(!curr.subNodes[subIx], "sub-Node already set ")
2426 	{
2427 		pragma(inline, true);
2428 		// "sub-Node at index " ~ subIx.to!string ~
2429 		// " already set to " ~ subNode.to!string);
2430 		curr.subNodes[subIx] = subNode;
2431 		return Branch(curr);
2432 	}
2433 
2434 	/** Set sub-`Node` of branch `Node curr` at index `ix` to `subNode`. */
2435 	Branch setSub(A)(auto ref A alloc, Branch curr, UIx subIx, Node subNode) pure nothrow @safe @nogc // TODO needs @nogc here
2436 	{
2437 		version (LDC) pragma(inline, true);
2438 		final switch (curr.typeIx) with (Branch.Ix)
2439 		{
2440 		case ix_SparseBranchPtr: return setSub(alloc, curr.as!(SparseBranch*), subIx, subNode);
2441 		case ix_DenseBranchPtr: return setSub(curr.as!(DenseBranch*), subIx, subNode);
2442 		case undefined:
2443 			assert(false);
2444 		}
2445 	}
2446 
2447 	/** Get sub-`Node` of branch `Node curr` at index `subIx`. */
2448 	Node getSub(Branch curr, UIx subIx) pure nothrow @safe @nogc
2449 	{
2450 		version (LDC) pragma(inline, true);
2451 		final switch (curr.typeIx) with (Branch.Ix)
2452 		{
2453 		case ix_SparseBranchPtr: return getSub(curr.as!(SparseBranch*), subIx);
2454 		case ix_DenseBranchPtr: return getSub(curr.as!(DenseBranch*), subIx);
2455 		case undefined:
2456 			assert(false);
2457 		}
2458 	}
2459 	/// ditto
2460 	Node getSub(SparseBranch* curr, UIx subIx) pure nothrow @safe @nogc
2461 	{
2462 		version (LDC) pragma(inline, true);
2463 		if (auto subNode = curr.subNodeAt(subIx))
2464 			return subNode;
2465 		return Node.init;
2466 	}
2467 	/// ditto
2468 	Node getSub(DenseBranch* curr, UIx subIx) pure nothrow @safe @nogc
2469 	{
2470 		pragma(inline, true);
2471 		auto sub = curr.subNodes[subIx];
2472 		debug curr.subNodes[subIx] = Node.init; // zero it to prevent multiple references
2473 		return sub;
2474 	}
2475 
2476 	/** Get leaves of node `curr`. */
2477 	inout(Leaf1!Value) getLeaf1(inout Branch curr) pure nothrow @safe @nogc
2478 	{
2479 		version (LDC) pragma(inline, true);
2480 		final switch (curr.typeIx) with (Branch.Ix)
2481 		{
2482 		case ix_SparseBranchPtr: return curr.as!(SparseBranch*).leaf1;
2483 		case ix_DenseBranchPtr: return curr.as!(DenseBranch*).leaf1;
2484 		case undefined:
2485 			assert(false);
2486 		}
2487 	}
2488 
2489 	/** Set direct leaves node of node `curr` to `leaf1`. */
2490 	void setLeaf1(Branch curr, Leaf1!Value leaf1) pure nothrow @safe @nogc
2491 	{
2492 		version (LDC) pragma(inline, true);
2493 		final switch (curr.typeIx) with (Branch.Ix)
2494 		{
2495 		case ix_SparseBranchPtr: curr.as!(SparseBranch*).leaf1 = leaf1; break;
2496 		case ix_DenseBranchPtr: curr.as!(DenseBranch*).leaf1 = leaf1; break;
2497 		case undefined:
2498 			assert(false);
2499 		}
2500 	}
2501 
2502 	/** Get prefix of node `curr`. */
2503 	auto getPrefix(inout Branch curr) pure nothrow @safe @nogc
2504 	{
2505 		version (LDC) pragma(inline, true);
2506 		final switch (curr.typeIx) with (Branch.Ix)
2507 		{
2508 		case ix_SparseBranchPtr: return curr.as!(SparseBranch*).prefix[];
2509 		case ix_DenseBranchPtr: return curr.as!(DenseBranch*).prefix[];
2510 		case undefined:
2511 			assert(false);
2512 		}
2513 	}
2514 
2515 	/** Set prefix of branch node `curr` to `prefix`. */
2516 	void setPrefix(Branch curr, const Ix[] prefix) pure nothrow @safe @nogc
2517 	{
2518 		pragma(inline, true);
2519 		final switch (curr.typeIx) with (Branch.Ix)
2520 		{
2521 		case ix_SparseBranchPtr: curr.as!(SparseBranch*).prefix = typeof(curr.as!(SparseBranch*).prefix)(prefix); break;
2522 		case ix_DenseBranchPtr: curr.as!(DenseBranch*).prefix = typeof(curr.as!(DenseBranch*).prefix)(prefix); break;
2523 		case undefined:
2524 			assert(false);
2525 		}
2526 	}
2527 
2528 	/** Pop `n` from prefix. */
2529 	void popFrontNPrefix(Branch curr, size_t n) pure nothrow @safe @nogc
2530 	{
2531 		version (LDC) pragma(inline, true);
2532 		final switch (curr.typeIx) with (Branch.Ix)
2533 		{
2534 		case ix_SparseBranchPtr: curr.as!(SparseBranch*).prefix.popFrontN(n); break;
2535 		case ix_DenseBranchPtr: curr.as!(DenseBranch*).prefix.popFrontN(n); break;
2536 		case undefined:
2537 			assert(false);
2538 		}
2539 	}
2540 
2541 	/** Get number of sub-nodes of node `curr`. */
2542 	SubCount getSubCount(inout Branch curr) pure nothrow @safe @nogc
2543 	{
2544 		version (LDC) pragma(inline, true);
2545 		final switch (curr.typeIx) with (Branch.Ix)
2546 		{
2547 		case ix_SparseBranchPtr: return cast(typeof(return))curr.as!(SparseBranch*).subCount;
2548 		case ix_DenseBranchPtr: return cast(typeof(return))curr.as!(DenseBranch*).subCount;
2549 		case undefined:
2550 			assert(false);
2551 		}
2552 	}
2553 
2554 	static if (isValue)
2555 	{
2556 		pure nothrow @safe @nogc:
2557 
2558 		/** Returns: `true` if `key` is stored under `curr`, `false` otherwise. */
2559 		inout(Value*) containsAt(inout Leaf1!Value curr, UKey key)
2560 		{
2561 			version (LDC) pragma(inline, true);
2562 			// debug if (willFail) { dbg("curr:", curr); }
2563 			// debug if (willFail) { dbg("key:", key); }
2564 			switch (curr.typeIx) with (Leaf1!Value.Ix)
2565 			{
2566 			case undefined:
2567 				return null;
2568 			case ix_SparseLeaf1Ptr: return key.length == 1 ? curr.as!(SparseLeaf1!Value*).contains(UIx(key[0])) : null;
2569 			case ix_DenseLeaf1Ptr:  return key.length == 1 ? curr.as!(DenseLeaf1!Value*).contains(UIx(key[0])) : null;
2570 			default: assert(false);
2571 			}
2572 		}
2573 
2574 		/// ditto
2575 		inout(Value*) containsAt(inout Node curr, UKey key) /* TODO: make @safe */ @trusted
2576 		in(key.length)
2577 		{
2578 			// debug if (willFail) { dbg("key:", key); }
2579 			import nxt.algorithm.searching : skipOver;
2580 			switch (curr.typeIx) with (Node.Ix)
2581 			{
2582 			case undefined:
2583 				return null;
2584 			case ix_SparseLeaf1Ptr: return key.length == 1 ? curr.as!(SparseLeaf1!Value*).contains(UIx(key[0])) : null;
2585 			case ix_DenseLeaf1Ptr:  return key.length == 1 ? curr.as!(DenseLeaf1!Value*).contains(UIx(key[0])) : null;
2586 			case ix_SparseBranchPtr:
2587 				auto curr_ = curr.as!(SparseBranch*);
2588 				if (key.skipOver(curr_.prefix[]))
2589 					return (key.length == 1 ?
2590 							containsAt(curr_.leaf1, key) : // in leaf
2591 							containsAt(curr_.subNodeAt(UIx(key[0])), key[1 .. $])); // recurse into branch tree
2592 				return null;
2593 			case ix_DenseBranchPtr:
2594 				auto curr_ = curr.as!(DenseBranch*);
2595 				if (key.skipOver(curr_.prefix[]))
2596 					return (key.length == 1 ?
2597 							containsAt(curr_.leaf1, key) : // in leaf
2598 							containsAt(curr_.subNodes[UIx(key[0])], key[1 .. $])); // recurse into branch tree
2599 				return null;
2600 			default: assert(false);
2601 			}
2602 		}
2603 	}
2604 	else
2605 	{
2606 		pure nothrow @safe @nogc:
2607 		const:
2608 
2609 		/** Returns: `true` if `key` is stored under `curr`, `false` otherwise. */
2610 		bool containsAt(Leaf1!Value curr, UKey key)
2611 		{
2612 			// debug if (willFail) { dbg("key:", key); }
2613 			final switch (curr.typeIx) with (Leaf1!Value.Ix)
2614 			{
2615 			case undefined:
2616 				return false;
2617 			case ix_HeptLeaf1: return curr.as!(HeptLeaf1).contains(key);
2618 			case ix_SparseLeaf1Ptr: return key.length == 1 && curr.as!(SparseLeaf1!Value*).contains(UIx(key[0]));
2619 			case ix_DenseLeaf1Ptr:  return key.length == 1 && curr.as!(DenseLeaf1!Value*).contains(UIx(key[0]));
2620 			}
2621 		}
2622 		/// ditto
2623 		bool containsAt(Node curr, UKey key) /* TODO: make @safe */ @trusted
2624 		in(key.length)
2625 		{
2626 			// debug if (willFail) { dbg("key:", key); }
2627 			import nxt.algorithm.searching : skipOver;
2628 			final switch (curr.typeIx) with (Node.Ix)
2629 			{
2630 			case undefined:
2631 				return false;
2632 			case ix_OneLeafMax7: return curr.as!(OneLeafMax7).contains(key);
2633 			case ix_TwoLeaf3: return curr.as!(TwoLeaf3).contains(key);
2634 			case ix_TriLeaf2: return curr.as!(TriLeaf2).contains(key);
2635 			case ix_HeptLeaf1: return curr.as!(HeptLeaf1).contains(key);
2636 			case ix_SparseLeaf1Ptr:
2637 				return key.length == 1 && curr.as!(SparseLeaf1!Value*).contains(UIx(key[0]));
2638 			case ix_DenseLeaf1Ptr:
2639 				return key.length == 1 && curr.as!(DenseLeaf1!Value*).contains(UIx(key[0]));
2640 			case ix_SparseBranchPtr:
2641 				auto curr_ = curr.as!(SparseBranch*);
2642 				return (key.skipOver(curr_.prefix) &&		// matching prefix
2643 						(key.length == 1 ?
2644 						 containsAt(curr_.leaf1, key) : // in leaf
2645 						 containsAt(curr_.subNodeAt(UIx(key[0])), key[1 .. $]))); // recurse into branch tree
2646 			case ix_DenseBranchPtr:
2647 				auto curr_ = curr.as!(DenseBranch*);
2648 				return (key.skipOver(curr_.prefix) &&		// matching prefix
2649 						(key.length == 1 ?
2650 						 containsAt(curr_.leaf1, key) : // in leaf
2651 						 containsAt(curr_.subNodes[UIx(key[0])], key[1 .. $]))); // recurse into branch tree
2652 			}
2653 		}
2654 	}
2655 
2656 	inout(Node) prefixAt(inout Node curr, UKey keyPrefix, out UKey keyPrefixRest) /* TODO: make @safe */ @trusted pure nothrow @nogc
2657 	{
2658 		import nxt.algorithm.searching : startsWith;
2659 		final switch (curr.typeIx) with (Node.Ix)
2660 		{
2661 		case undefined:
2662 			return typeof(return).init; // terminate recursion
2663 		case ix_OneLeafMax7:
2664 			if (curr.as!(OneLeafMax7).key[].startsWith(keyPrefix)) { goto processHit; }
2665 			break;
2666 		case ix_TwoLeaf3:
2667 			if (curr.as!(TwoLeaf3).keyLength >= keyPrefix.length) { goto processHit; }
2668 			break;
2669 		case ix_TriLeaf2:
2670 			if (curr.as!(TriLeaf2).keyLength >= keyPrefix.length) { goto processHit; }
2671 			break;
2672 		case ix_HeptLeaf1:
2673 			if (curr.as!(HeptLeaf1).keyLength >= keyPrefix.length) { goto processHit; }
2674 			break;
2675 		case ix_SparseLeaf1Ptr:
2676 		case ix_DenseLeaf1Ptr:
2677 			if (keyPrefix.length <= 1) { goto processHit; }
2678 			break;
2679 		case ix_SparseBranchPtr:
2680 			auto curr_ = curr.as!(SparseBranch*);
2681 			if (keyPrefix.startsWith(curr_.prefix[]))
2682 			{
2683 				immutable currPrefixLength = curr_.prefix.length;
2684 				if (keyPrefix.length == currPrefixLength || // if no more prefix
2685 					(curr_.leaf1 && // both leaf1
2686 					 curr_.subCount)) // and sub-nodes
2687 					goto processHit;
2688 				else if (curr_.subCount == 0) // only leaf1
2689 					return prefixAt(Node(curr_.leaf1),
2690 									keyPrefix[currPrefixLength .. $],
2691 									keyPrefixRest);
2692 				else		// only sub-node(s)
2693 					return prefixAt(curr_.subNodeAt(UIx(keyPrefix[currPrefixLength])),
2694 									keyPrefix[currPrefixLength + 1 .. $],
2695 									keyPrefixRest);
2696 			}
2697 			break;
2698 		case ix_DenseBranchPtr:
2699 			auto curr_ = curr.as!(DenseBranch*);
2700 			if (keyPrefix.startsWith(curr_.prefix[]))
2701 			{
2702 				immutable currPrefixLength = curr_.prefix.length;
2703 				if (keyPrefix.length == currPrefixLength || // if no more prefix
2704 					(curr_.leaf1 && // both leaf1
2705 					 curr_.subCount)) // and sub-nodes
2706 					goto processHit;
2707 				else if (curr_.subCount == 0) // only leaf1
2708 					return prefixAt(Node(curr_.leaf1),
2709 									keyPrefix[currPrefixLength .. $],
2710 									keyPrefixRest);
2711 				else		// only sub-node(s)
2712 					return prefixAt(curr_.subNodes[UIx(keyPrefix[currPrefixLength])],
2713 									keyPrefix[currPrefixLength + 1 .. $],
2714 									keyPrefixRest);
2715 			}
2716 			break;
2717 		}
2718 		return typeof(return).init;
2719 	processHit:
2720 		keyPrefixRest = keyPrefix;
2721 		return curr;
2722 	}
2723 
2724 	inout(Node) matchCommonPrefixAt(inout Node curr, UKey key, out UKey keyRest) /* TODO: make @safe */ @trusted pure nothrow @nogc
2725 	{
2726 		// dbg(curr.typeIx);
2727 		import nxt.algorithm.searching : startsWith;
2728 		final switch (curr.typeIx) with (Node.Ix)
2729 		{
2730 		case undefined:
2731 			return typeof(return).init; // terminate recursion
2732 		case ix_OneLeafMax7:
2733 		case ix_TwoLeaf3:
2734 		case ix_TriLeaf2:
2735 		case ix_HeptLeaf1:
2736 		case ix_SparseLeaf1Ptr:
2737 		case ix_DenseLeaf1Ptr:
2738 			goto processHit;
2739 		case ix_SparseBranchPtr:
2740 			auto curr_ = curr.as!(SparseBranch*);
2741 			// dbg(key);
2742 			// dbg(curr_.prefix[]);
2743 			if (key.startsWith(curr_.prefix[]))
2744 			{
2745 				immutable currPrefixLength = curr_.prefix.length;
2746 				if (key.length == currPrefixLength || // if no more prefix
2747 					(curr_.leaf1 && // both leaf1
2748 					 curr_.subCount)) // and sub-nodes
2749 					goto processHit;
2750 				else if (curr_.subCount == 0) // only leaf1
2751 					return matchCommonPrefixAt(Node(curr_.leaf1),
2752 											   key[currPrefixLength .. $],
2753 											   keyRest);
2754 				else if (curr_.subCount == 1) // only one sub node
2755 					return matchCommonPrefixAt(curr_.subNodeAt(UIx(key[currPrefixLength])),
2756 											   key[currPrefixLength + 1 .. $],
2757 											   keyRest);
2758 				else
2759 					goto processHit;
2760 			}
2761 			break;
2762 		case ix_DenseBranchPtr:
2763 			auto curr_ = curr.as!(DenseBranch*);
2764 			if (key.startsWith(curr_.prefix[]))
2765 			{
2766 				immutable currPrefixLength = curr_.prefix.length;
2767 				if (key.length == currPrefixLength || // if no more prefix
2768 					(curr_.leaf1 && // both leaf1
2769 					 curr_.subCount)) // and sub-nodes
2770 					goto processHit;
2771 				else if (curr_.subCount == 0) // only leaf1
2772 					return matchCommonPrefixAt(Node(curr_.leaf1),
2773 											   key[currPrefixLength .. $],
2774 											   keyRest);
2775 				else if (curr_.subCount == 1) // only one sub node
2776 					return matchCommonPrefixAt(curr_.subNodes[UIx(key[currPrefixLength])],
2777 											   key[currPrefixLength + 1 .. $],
2778 											   keyRest);
2779 				else
2780 					goto processHit;
2781 			}
2782 			break;
2783 		}
2784 		return typeof(return).init;
2785 	processHit:
2786 		keyRest = key;
2787 		return curr;
2788 	}
2789 
2790 	size_t countHeapNodesAt(Node curr) pure nothrow @safe @nogc
2791 	{
2792 		size_t count = 0;
2793 		final switch (curr.typeIx) with (Node.Ix)
2794 		{
2795 		case undefined:
2796 			break;  // propagate undefined
2797 		case ix_OneLeafMax7: break;
2798 		case ix_TwoLeaf3: break;
2799 		case ix_TriLeaf2: break;
2800 		case ix_HeptLeaf1: break;
2801 		case ix_SparseLeaf1Ptr:
2802 		case ix_DenseLeaf1Ptr:
2803 			++count;
2804 			break;
2805 		case ix_SparseBranchPtr:
2806 			auto curr_ = curr.as!(SparseBranch*);
2807 			++count;
2808 			foreach (subNode; curr_.subNodeSlots[0 .. curr_.subCount])
2809 				if (subNode)
2810 					count += countHeapNodesAt(subNode);
2811 			break;
2812 		case ix_DenseBranchPtr:
2813 			++count;
2814 			auto curr_ = curr.as!(DenseBranch*);
2815 			foreach (subNode; curr_.subNodes)
2816 				if (subNode)
2817 					count += countHeapNodesAt(subNode);
2818 			break;
2819 		}
2820 		return count;
2821 	}
2822 
2823 	/** Returns a duplicate of this tree with root at `curr`.
2824 		Shallowly duplicates the values in the map case.
2825 	*/
2826 	Leaf1!Value treedup(A)(Leaf1!Value curr, auto ref A alloc) @safe pure nothrow
2827 	{
2828 		final switch (curr.typeIx) with (Leaf1!Value.Ix)
2829 		{
2830 		case undefined:		 // propagate undefined
2831 		case ix_HeptLeaf1: return curr; // value semantics so just copy
2832 		case ix_SparseLeaf1Ptr: return typeof(return)(curr.as!(SparseLeaf1!Value*).dupAt(alloc));
2833 		case ix_DenseLeaf1Ptr: return typeof(return)(curr.as!(DenseLeaf1!Value*).dupAt(alloc));
2834 		}
2835 	}
2836 
2837 	/** Returns a duplicate of this tree with root at `curr`.
2838 		Shallowly duplicates the values in the map case.
2839 	*/
2840 	Node treedup(A)(Node curr, auto ref A alloc) pure nothrow @safe @nogc
2841 	{
2842 		final switch (curr.typeIx) with (Node.Ix)
2843 		{
2844 		case undefined:		 // propagate undefined
2845 		case ix_OneLeafMax7:
2846 		case ix_TwoLeaf3:
2847 		case ix_TriLeaf2:
2848 		case ix_HeptLeaf1: return curr; // value semantics so just copy
2849 		case ix_SparseLeaf1Ptr: return typeof(return)(curr.as!(SparseLeaf1!Value*).dupAt(alloc));
2850 		case ix_DenseLeaf1Ptr: return typeof(return)(curr.as!(DenseLeaf1!Value*).dupAt(alloc));
2851 		case ix_SparseBranchPtr: return typeof(return)(curr.as!(SparseBranch*).dupAt(alloc));
2852 		case ix_DenseBranchPtr: return typeof(return)(curr.as!(DenseBranch*).dupAt(alloc));
2853 		}
2854 	}
2855 
2856 	static if (!isValue)
2857 	{
2858 		Node insertNew(A)(auto ref A alloc, UKey key, out ElementRef elementRef) pure nothrow @safe @nogc
2859 		{
2860 			Node next;
2861 			// debug if (willFail) { dbg("WILL FAIL: key:", key); }
2862 			switch (key.length)
2863 			{
2864 			case 0: assert(false, "key must not be empty"); // return elementRef = Node(makeFixedSizeNode!(OneLeafMax7)());
2865 			case 1: next = Node(makeFixedSizeNodeValue!(HeptLeaf1)(key[0])); break;
2866 			case 2: next = Node(makeFixedSizeNodeValue!(TriLeaf2)(key)); break;
2867 			case 3: next = Node(makeFixedSizeNodeValue!(TwoLeaf3)(key)); break;
2868 			default:
2869 				if (key.length <= OneLeafMax7.capacity)
2870 				{
2871 					next = Node(makeFixedSizeNodeValue!(OneLeafMax7)(key));
2872 					break;
2873 				}
2874 				else				// key doesn't fit in a `OneLeafMax7`
2875 					return Node(insertNewBranch(alloc, key, elementRef));
2876 			}
2877 			elementRef = ElementRef(next,
2878 									UIx(0), // always first index
2879 									ModStatus.added);
2880 			return next;
2881 		}
2882 	}
2883 
2884 	Branch insertNewBranch(A)(auto ref A alloc, Elt!Value elt, out ElementRef elementRef) pure nothrow @safe @nogc
2885 	{
2886 		// debug if (willFail) { dbg("WILL FAIL: elt:", elt); }
2887 		auto key = eltKey!Value(elt);
2888 		assert(key.length);
2889 		immutable prefixLength = min(key.length - 1, // all but last Ix of key
2890 								 DefaultBranch.prefixCapacity); // as much as possible of key in branch prefix
2891 		auto prefix = key[0 .. prefixLength];
2892 		typeof(return) next = insertAtBelowPrefix(alloc,
2893 												  Branch(makeVariableSizeNodePointer!(DefaultBranch)(alloc, 1, prefix)),
2894 												  eltKeyDropExactly!Value(elt, prefixLength), elementRef);
2895 		assert(elementRef);
2896 		return next;
2897 	}
2898 
2899 	/** Insert `key` into sub-tree under root `curr`. */
2900 	Node insertAt(A)(auto ref A alloc, Node curr, Elt!Value elt, out ElementRef elementRef) pure nothrow @safe @nogc
2901 	{
2902 		auto key = eltKey!Value(elt);
2903 		// debug if (willFail) { dbg("WILL FAIL: key:", key, " curr:", curr); }
2904 		assert(key.length);
2905 
2906 		if (!curr)		  // if no existing `Node` to insert at
2907 		{
2908 			static if (isValue)
2909 				auto next = Node(insertNewBranch(alloc, elt, elementRef));
2910 			else
2911 				auto next = insertNew(alloc, key, elementRef);
2912 			assert(elementRef); // must be added to new Node
2913 			return next;
2914 		}
2915 		else
2916 		{
2917 			final switch (curr.typeIx) with (Node.Ix)
2918 			{
2919 			case undefined:
2920 				return typeof(return).init;
2921 			case ix_OneLeafMax7:
2922 				static if (isValue)
2923 					assert(false);
2924 				else
2925 					return insertAt(alloc, curr.as!(OneLeafMax7), key, elementRef);
2926 			case ix_TwoLeaf3:
2927 				static if (isValue)
2928 					assert(false);
2929 				else
2930 					return insertAt(alloc, curr.as!(TwoLeaf3), key, elementRef);
2931 			case ix_TriLeaf2:
2932 				static if (isValue)
2933 					assert(false);
2934 				else
2935 					return insertAt(alloc, curr.as!(TriLeaf2), key, elementRef);
2936 			case ix_HeptLeaf1:
2937 				static if (isValue)
2938 					assert(false);
2939 				else
2940 					return insertAt(alloc, curr.as!(HeptLeaf1), key, elementRef);
2941 			case ix_SparseLeaf1Ptr:
2942 				return insertAtLeaf(alloc, Leaf1!Value(curr.as!(SparseLeaf1!Value*)), elt, elementRef); /+ TODO: use toLeaf(curr) +/
2943 			case ix_DenseLeaf1Ptr:
2944 				return insertAtLeaf(alloc, Leaf1!Value(curr.as!(DenseLeaf1!Value*)), elt, elementRef); /+ TODO: use toLeaf(curr) +/
2945 			case ix_SparseBranchPtr:
2946 				// debug if (willFail) { dbg("WILL FAIL: currPrefix:", curr.as!(SparseBranch*).prefix); }
2947 				return Node(insertAtAbovePrefix(alloc, Branch(curr.as!(SparseBranch*)), elt, elementRef));
2948 			case ix_DenseBranchPtr:
2949 				return Node(insertAtAbovePrefix(alloc, Branch(curr.as!(DenseBranch*)), elt, elementRef));
2950 			}
2951 		}
2952 	}
2953 
2954 	/** Insert `key` into sub-tree under branch `curr` above prefix, that is
2955 		the prefix of `curr` is stripped from `key` prior to insertion. */
2956 	Branch insertAtAbovePrefix(A)(auto ref A alloc, Branch curr, Elt!Value elt, out ElementRef elementRef) pure nothrow @safe @nogc
2957 	{
2958 		auto key = eltKey!Value(elt);
2959 		assert(key.length);
2960 
2961 		import std.algorithm.searching : commonPrefix;
2962 		auto currPrefix = getPrefix(curr);
2963 		auto matchedKeyPrefix = commonPrefix(key, currPrefix);
2964 
2965 		// debug if (willFail) { dbg("WILL FAIL: key:", key,
2966 		//					 " curr:", curr,
2967 		//					 " currPrefix:", getPrefix(curr),
2968 		//					 " matchedKeyPrefix:", matchedKeyPrefix); }
2969 
2970 		if (matchedKeyPrefix.length == 0) // no prefix key match
2971 		{
2972 			if (currPrefix.length == 0) // no current prefix
2973 			{
2974 				// debug if (willFail) { dbg("WILL FAIL"); }
2975 				// NOTE: prefix:"", key:"cd"
2976 				return insertAtBelowPrefix(alloc, curr, elt, elementRef);
2977 			}
2978 			else  // if (currPrefix.length != 0) // non-empty current prefix
2979 			{
2980 				// NOTE: prefix:"ab", key:"cd"
2981 				immutable currSubIx = UIx(currPrefix[0]); // subIx = 'a'
2982 				if (currPrefix.length == 1 && getSubCount(curr) == 0) // if `curr`-prefix become empty and only leaf pointer
2983 				{
2984 					// debug if (willFail) { dbg("WILL FAIL"); }
2985 					popFrontNPrefix(curr, 1);
2986 					curr = setSub(alloc, curr, currSubIx, Node(getLeaf1(curr))); // move it to sub
2987 					setLeaf1(curr, Leaf1!Value.init);
2988 
2989 					return insertAtBelowPrefix(alloc, curr, elt, elementRef); // directly call below because `curr`-prefix is now empty
2990 				}
2991 				else
2992 				{
2993 					// debug if (willFail) { dbg("WILL FAIL"); }
2994 					popFrontNPrefix(curr, 1);
2995 					auto next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, 2, null, IxSub(currSubIx, Node(curr)));
2996 					return insertAtAbovePrefix(alloc, Branch(next), elt, elementRef);
2997 				}
2998 			}
2999 		}
3000 		else if (matchedKeyPrefix.length < key.length)
3001 		{
3002 			if (matchedKeyPrefix.length == currPrefix.length)
3003 			{
3004 				// debug if (willFail) { dbg("WILL FAIL"); }
3005 				// NOTE: key is an extension of prefix: prefix:"ab", key:"abcd"
3006 				return insertAtBelowPrefix(alloc, curr, eltKeyDropExactly!Value(elt, currPrefix.length), elementRef);
3007 			}
3008 			else
3009 			{
3010 				// debug if (willFail) { dbg("WILL FAIL"); }
3011 				// NOTE: prefix and key share beginning: prefix:"ab11", key:"ab22"
3012 				immutable currSubIx = UIx(currPrefix[matchedKeyPrefix.length]); // need index first before we modify curr.prefix
3013 				popFrontNPrefix(curr, matchedKeyPrefix.length + 1);
3014 				auto next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, 2, matchedKeyPrefix, IxSub(currSubIx, Node(curr)));
3015 				return insertAtBelowPrefix(alloc, Branch(next), eltKeyDropExactly!Value(elt, matchedKeyPrefix.length), elementRef);
3016 			}
3017 		}
3018 		else // if (matchedKeyPrefix.length == key.length)
3019 		{
3020 			// debug if (willFail) { dbg("WILL FAIL"); }
3021 			assert(matchedKeyPrefix.length == key.length);
3022 			if (matchedKeyPrefix.length < currPrefix.length)
3023 			{
3024 				// NOTE: prefix is an extension of key: prefix:"abcd", key:"ab"
3025 				assert(matchedKeyPrefix.length);
3026 				immutable nextPrefixLength = matchedKeyPrefix.length - 1;
3027 				immutable currSubIx = UIx(currPrefix[nextPrefixLength]); // need index first
3028 				popFrontNPrefix(curr, matchedKeyPrefix.length); // drop matchedKeyPrefix plus index to next super branch
3029 				auto next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, 2, matchedKeyPrefix[0 .. $ - 1], IxSub(currSubIx, Node(curr)));
3030 				return insertAtBelowPrefix(alloc, Branch(next), eltKeyDropExactly!Value(elt, nextPrefixLength), elementRef);
3031 			}
3032 			else /* if (matchedKeyPrefix.length == currPrefix.length) and in turn
3033 					if (key.length == currPrefix.length */
3034 			{
3035 				// NOTE: prefix equals key: prefix:"abcd", key:"abcd"
3036 				assert(matchedKeyPrefix.length);
3037 				immutable currSubIx = UIx(currPrefix[matchedKeyPrefix.length - 1]); // need index first
3038 				popFrontNPrefix(curr, matchedKeyPrefix.length); // drop matchedKeyPrefix plus index to next super branch
3039 				auto next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, 2, matchedKeyPrefix[0 .. $ - 1], IxSub(currSubIx, Node(curr)));
3040 				static if (isValue)
3041 					return insertAtLeaf1(alloc, Branch(next), UIx(key[$ - 1]), elt.value, elementRef);
3042 				else
3043 					return insertAtLeaf1(alloc, Branch(next), UIx(key[$ - 1]), elementRef);
3044 			}
3045 		}
3046 	}
3047 
3048 	/** Like `insertAtAbovePrefix` but also asserts that `key` is
3049 		currently not stored under `curr`. */
3050 	pragma(inline) Branch insertNewAtAbovePrefix(A)(auto ref A alloc, Branch curr, Elt!Value elt) pure nothrow @safe @nogc
3051 	{
3052 		ElementRef elementRef;
3053 		auto next = insertAtAbovePrefix(alloc, curr, elt, elementRef);
3054 		assert(elementRef);
3055 		return next;
3056 	}
3057 
3058 	static if (isValue)
3059 	{
3060 		pragma(inline) Branch insertAtSubNode(A)(auto ref A alloc, Branch curr, UKey key, Value value, out ElementRef elementRef) pure nothrow @safe @nogc
3061 		{
3062 			// debug if (willFail) { dbg("WILL FAIL"); }
3063 			immutable subIx = UIx(key[0]);
3064 			return setSub(alloc, curr, subIx,
3065 						  insertAt(alloc,
3066 								   getSub(curr, subIx), // recurse
3067 								   Elt!Value(key[1 .. $], value),
3068 								   elementRef));
3069 		}
3070 	}
3071 	else
3072 	{
3073 		pragma(inline) Branch insertAtSubNode(A)(auto ref A alloc, Branch curr, UKey key, out ElementRef elementRef) pure nothrow @safe @nogc
3074 		{
3075 			// debug if (willFail) { dbg("WILL FAIL"); }
3076 			immutable subIx = UIx(key[0]);
3077 			return setSub(alloc, curr, subIx,
3078 						  insertAt(alloc, getSub(curr, subIx), // recurse
3079 								   key[1 .. $],
3080 								   elementRef));
3081 		}
3082 	}
3083 
3084 	/** Insert `key` into sub-tree under branch `curr` below prefix, that is
3085 		the prefix of `curr` is not stripped from `key` prior to
3086 		insertion. */
3087 	Branch insertAtBelowPrefix(A)(auto ref A alloc, Branch curr, Elt!Value elt, out ElementRef elementRef) pure nothrow @safe @nogc
3088 	{
3089 		auto key = eltKey!Value(elt);
3090 		assert(key.length);
3091 		// debug if (willFail) { dbg("WILL FAIL: key:", key,
3092 		//						   " curr:", curr,
3093 		//						   " currPrefix:", getPrefix(curr),
3094 		//						   " elementRef:", elementRef); }
3095 		if (key.length == 1)
3096 		{
3097 			static if (isValue)
3098 				return insertAtLeaf1(alloc, curr, UIx(key[0]), elt.value, elementRef);
3099 			else
3100 				return insertAtLeaf1(alloc, curr, UIx(key[0]), elementRef);
3101 		}
3102 		else				// key.length >= 2
3103 		{
3104 			static if (isValue)
3105 				return insertAtSubNode(alloc, curr, key, elt.value, elementRef);
3106 			else
3107 				return insertAtSubNode(alloc, curr, key, elementRef);
3108 		}
3109 	}
3110 
3111 	Branch insertNewAtBelowPrefix(A)(auto ref A alloc, Branch curr, Elt!Value elt) pure nothrow @safe @nogc
3112 	{
3113 		ElementRef elementRef;
3114 		auto next = insertAtBelowPrefix(alloc, curr, elt, elementRef);
3115 		assert(elementRef);
3116 		return next;
3117 	}
3118 
3119 	Leaf1!Value insertIxAtLeaftoLeaf(A)(auto ref A alloc,
3120 												Leaf1!Value curr,
3121 												IxElt!Value elt,
3122 												out ElementRef elementRef) pure nothrow @safe @nogc
3123 	{
3124 		auto key = eltIx!Value(elt);
3125 		// debug if (willFail) { dbg("WILL FAIL: elt:", elt,
3126 		//						   " curr:", curr,
3127 		//						   " elementRef:", elementRef); }
3128 		switch (curr.typeIx) with (Leaf1!Value.Ix)
3129 		{
3130 		case undefined:
3131 			return typeof(return).init;
3132 		case ix_HeptLeaf1:
3133 			static if (isValue)
3134 				assert(false);
3135 			else
3136 				return insertAt(alloc, curr.as!(HeptLeaf1), key, elementRef); // possibly expanded to other Leaf1!Value
3137 		case ix_SparseLeaf1Ptr:
3138 			SparseLeaf1!Value* curr_ = curr.as!(SparseLeaf1!Value*);
3139 			size_t index;
3140 			ModStatus modStatus;
3141 			curr_ = curr_.reconstructingInsert(alloc, elt, modStatus, index);
3142 			curr = Leaf1!Value(curr_);
3143 			final switch (modStatus)
3144 			{
3145 			case ModStatus.unchanged: // already stored at `index`
3146 				elementRef = ElementRef(Node(curr_), UIx(index), modStatus);
3147 				return curr;
3148 			case ModStatus.added:
3149 				elementRef = ElementRef(Node(curr_), UIx(index), modStatus);
3150 				return curr;
3151 			case ModStatus.maxCapacityReached:
3152 				auto next = insertIxAtLeaftoLeaf(alloc,
3153 												 expand(alloc, curr_, 1), // make room for one more
3154 												 elt, elementRef);
3155 				assert(next.peek!(DenseLeaf1!Value*));
3156 				return next;
3157 			case ModStatus.updated:
3158 				elementRef = ElementRef(Node(curr_), UIx(index), modStatus);
3159 				return curr;
3160 			}
3161 		case ix_DenseLeaf1Ptr:
3162 			immutable modStatus = curr.as!(DenseLeaf1!Value*).insert(elt);
3163 			static if (isValue)
3164 				immutable ix = elt.ix;
3165 			else
3166 				immutable ix = elt;
3167 			elementRef = ElementRef(Node(curr), ix, modStatus);
3168 			break;
3169 		default:
3170 			assert(false, "Unsupported Leaf1!Value type " // ~ curr.typeIx.to!string
3171 				);
3172 		}
3173 		return curr;
3174 	}
3175 
3176 	static if (isValue)
3177 	{
3178 		Branch insertAtLeaf1(A)(auto ref A alloc, Branch curr, UIx key, Value value, out ElementRef elementRef) pure nothrow @safe @nogc
3179 		{
3180 			// debug if (willFail) { dbg("WILL FAIL: key:", key,
3181 			//						   " value:", value,
3182 			//						   " curr:", curr,
3183 			//						   " currPrefix:", getPrefix(curr),
3184 			//						   " elementRef:", elementRef); }
3185 			if (auto leaf = getLeaf1(curr))
3186 				setLeaf1(curr, insertIxAtLeaftoLeaf(alloc, leaf, IxElt!Value(key, value), elementRef));
3187 			else
3188 			{
3189 				Ix[1] ixs = [Ix(key)]; /+ TODO: scope +/
3190 				Value[1] values = [value]; /+ TODO: scope +/
3191 				auto leaf_ = makeVariableSizeNodePointer!(SparseLeaf1!Value)(alloc, 1, ixs, values); // needed for values
3192 				elementRef = ElementRef(Node(leaf_), UIx(0), ModStatus.added);
3193 				setLeaf1(curr, Leaf1!Value(leaf_));
3194 			}
3195 			return curr;
3196 		}
3197 	}
3198 	else
3199 	{
3200 		Branch insertAtLeaf1(A)(auto ref A alloc, Branch curr, UIx key, out ElementRef elementRef) pure nothrow @safe @nogc
3201 		{
3202 			// debug if (willFail) { dbg("WILL FAIL: key:", key,
3203 			//						   " curr:", curr,
3204 			//						   " currPrefix:", getPrefix(curr),
3205 			//						   " elementRef:", elementRef); }
3206 			if (auto leaf = getLeaf1(curr))
3207 				setLeaf1(curr, insertIxAtLeaftoLeaf(alloc, leaf, key, elementRef));
3208 			else
3209 			{
3210 				auto leaf_ = makeFixedSizeNodeValue!(HeptLeaf1)(key); // can pack more efficiently when no value
3211 				elementRef = ElementRef(Node(leaf_), UIx(0), ModStatus.added);
3212 				setLeaf1(curr, Leaf1!Value(leaf_));
3213 			}
3214 			return curr;
3215 		}
3216 	}
3217 
3218 	Node insertAtLeaf(A)(auto ref A alloc, Leaf1!Value curr, Elt!Value elt, out ElementRef elementRef) pure nothrow @safe @nogc
3219 	{
3220 		// debug if (willFail) { dbg("WILL FAIL: elt:", elt); }
3221 		auto key = eltKey!Value(elt);
3222 		assert(key.length);
3223 		if (key.length == 1)
3224 		{
3225 			static if (isValue)
3226 				return Node(insertIxAtLeaftoLeaf(alloc, curr, IxElt!Value(UIx(key[0]), elt.value), elementRef));
3227 			else
3228 				return Node(insertIxAtLeaftoLeaf(alloc, curr, UIx(key[0]), elementRef));
3229 		}
3230 		else
3231 		{
3232 			assert(key.length >= 2);
3233 			immutable prefixLength = key.length - 2; // >= 0
3234 			const nextPrefix = key[0 .. prefixLength];
3235 			auto next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, 1, nextPrefix, curr); // one sub-node and one leaf
3236 			return Node(insertAtBelowPrefix(alloc, Branch(next), eltKeyDropExactly!Value(elt, prefixLength), elementRef));
3237 		}
3238 	}
3239 
3240 	static if (!isValue)
3241 	{
3242 		Node insertAt(A)(auto ref A alloc, OneLeafMax7 curr, UKey key, out ElementRef elementRef) pure nothrow @safe @nogc
3243 		{
3244 			assert(curr.key.length);
3245 			// debug if (willFail) { dbg("WILL FAIL: key:", key, " curr.key:", curr.key); }
3246 
3247 			import std.algorithm.searching : commonPrefix;
3248 			auto matchedKeyPrefix = commonPrefix(key, curr.key);
3249 			if (curr.key.length == key.length)
3250 			{
3251 				if (matchedKeyPrefix.length == key.length) // curr.key, key and matchedKeyPrefix all equal
3252 					return Node(curr); // already stored in `curr`
3253 				else if (matchedKeyPrefix.length + 1 == key.length) // key and curr.key are both matchedKeyPrefix plus one extra
3254 				{
3255 					/+ TODO: functionize: +/
3256 					Node next;
3257 					switch (matchedKeyPrefix.length)
3258 					{
3259 					case 0:
3260 						next = makeFixedSizeNodeValue!(HeptLeaf1)(curr.key[0], key[0]);
3261 						elementRef = ElementRef(next, UIx(1), ModStatus.added);
3262 						break;
3263 					case 1:
3264 						next = makeFixedSizeNodeValue!(TriLeaf2)(curr.key, key);
3265 						elementRef = ElementRef(next, UIx(1), ModStatus.added);
3266 						break;
3267 					case 2:
3268 						next = makeFixedSizeNodeValue!(TwoLeaf3)(curr.key, key);
3269 						elementRef = ElementRef(next, UIx(1), ModStatus.added);
3270 						break;
3271 					default:
3272 						const nextPrefix = matchedKeyPrefix[0 .. min(matchedKeyPrefix.length,
3273 																	 DefaultBranch.prefixCapacity)]; // limit prefix branch capacity
3274 						Branch nextBranch = makeVariableSizeNodePointer!(DefaultBranch)(alloc, 1 + 1, // `curr` and `key`
3275 																							nextPrefix);
3276 						nextBranch = insertNewAtBelowPrefix(alloc, nextBranch, curr.key[nextPrefix.length .. $]);
3277 						nextBranch = insertAtBelowPrefix(alloc, nextBranch, key[nextPrefix.length .. $], elementRef);
3278 						assert(elementRef);
3279 						next = Node(nextBranch);
3280 						break;
3281 					}
3282 					freeNode(alloc, curr);
3283 					return next;
3284 				}
3285 			}
3286 
3287 			return Node(insertAtAbovePrefix(alloc, expand(alloc, curr), key, elementRef));
3288 		}
3289 
3290 		Node insertAt(A)(auto ref A alloc, TwoLeaf3 curr, UKey key, out ElementRef elementRef) pure nothrow @safe @nogc
3291 		{
3292 			if (curr.keyLength == key.length)
3293 			{
3294 				if (curr.contains(key))
3295 					return Node(curr);
3296 				if (!curr.keys.full)
3297 				{
3298 					assert(curr.keys.length == 1);
3299 					elementRef = ElementRef(Node(curr), UIx(curr.keys.length), ModStatus.added);
3300 					curr.keys.pushBack(key);
3301 					return Node(curr);
3302 				}
3303 			}
3304 			return Node(insertAtAbovePrefix(alloc, expand(alloc, curr), key, elementRef)); // NOTE stay at same (depth)
3305 		}
3306 
3307 		Node insertAt(A)(auto ref A alloc, TriLeaf2 curr, UKey key, out ElementRef elementRef) pure nothrow @safe @nogc
3308 		{
3309 			if (curr.keyLength == key.length)
3310 			{
3311 				if (curr.contains(key))
3312 					return Node(curr);
3313 				if (!curr.keys.full)
3314 				{
3315 					elementRef = ElementRef(Node(curr), UIx(curr.keys.length), ModStatus.added);
3316 					curr.keys.pushBack(key);
3317 					return Node(curr);
3318 				}
3319 			}
3320 			return Node(insertAtAbovePrefix(alloc, expand(alloc, curr), key, elementRef)); // NOTE stay at same (depth)
3321 		}
3322 
3323 		Leaf1!Value insertAt(A)(auto ref A alloc, HeptLeaf1 curr, UIx key, out ElementRef elementRef) pure nothrow @safe @nogc
3324 		{
3325 			if (curr.contains(key))
3326 				return Leaf1!Value(curr);
3327 			if (!curr.keys.full)
3328 			{
3329 				elementRef = ElementRef(Node(curr), UIx(curr.keys.back), ModStatus.added);
3330 				curr.keys.pushBack(cast(Ix)key);
3331 				return Leaf1!Value(curr);
3332 			}
3333 			else			// curr is full
3334 			{
3335 				assert(curr.keys.length == curr.capacity);
3336 
3337 				// pack `curr.keys` plus `key` into `nextKeys`
3338 				Ix[curr.capacity + 1] nextKeys;
3339 				nextKeys[0 .. curr.capacity] = curr.keys;
3340 				nextKeys[curr.capacity] = cast(Ix)key;
3341 
3342 				import std.algorithm.sorting : sort;
3343 				sort(nextKeys[]); /+ TODO: move this sorting elsewhere +/
3344 
3345 				auto next = makeVariableSizeNodePointer!(SparseLeaf1!Value)(alloc, nextKeys.length, nextKeys[]);
3346 				elementRef = ElementRef(Node(next), UIx(curr.capacity), ModStatus.added);
3347 
3348 				freeNode(alloc, curr);
3349 				return Leaf1!Value(next);
3350 			}
3351 		}
3352 
3353 		Node insertAt(A)(auto ref A alloc, HeptLeaf1 curr, UKey key, out ElementRef elementRef) pure nothrow @safe @nogc
3354 		{
3355 			if (curr.keyLength == key.length)
3356 				return Node(insertAt(alloc, curr, UIx(key[0]), elementRef)); // use `Ix key`-overload
3357 			return insertAt(alloc, Node(makeVariableSizeNodePointer!(DefaultBranch)(alloc, 1, Leaf1!Value(curr))), // current `key`
3358 							key, elementRef); // NOTE stay at same (depth)
3359 		}
3360 
3361 		/** Split `curr` using `prefix`. */
3362 		Node split(A)(auto ref A alloc, OneLeafMax7 curr, UKey prefix, UKey key) /+ TODO: key here is a bit malplaced +/
3363 			pure nothrow @safe @nogc
3364 		{
3365 			assert(key.length);
3366 
3367 			if (curr.key.length == key.length) // balanced tree possible
3368 			{
3369 				switch (curr.key.length)
3370 				{
3371 				case 1:
3372 					if (prefix.length == 0)
3373 					{
3374 						freeNode(alloc, curr);
3375 						return Node(makeFixedSizeNodeValue!(HeptLeaf1)(curr.key)); /+ TODO: removing parameter has no effect. why? +/
3376 					}
3377 					break;
3378 				case 2:
3379 					freeNode(alloc, curr);
3380 					return Node(makeFixedSizeNodeValue!(TriLeaf2)(curr.key));
3381 				case 3:
3382 					freeNode(alloc, curr);
3383 					return Node(makeFixedSizeNodeValue!(TwoLeaf3)(curr.key));
3384 				default:
3385 					break;
3386 				}
3387 			}
3388 
3389 			// default case
3390 			Branch next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, 1 + 1, prefix); // current plus one more
3391 			next = insertNewAtBelowPrefix(alloc, next, curr.key[prefix.length .. $]);
3392 			freeNode(alloc, curr);   // remove old current
3393 
3394 			return Node(next);
3395 		}
3396 
3397 		/** Destructively expand `curr` to make room for `capacityIncrement` more keys and return it. */
3398 		Branch expand(A)(auto ref A alloc, OneLeafMax7 curr, in size_t capacityIncrement = 1) pure nothrow @safe @nogc
3399 		{
3400 			assert(curr.key.length >= 2);
3401 			typeof(return) next;
3402 
3403 			if (curr.key.length <= DefaultBranch.prefixCapacity + 1) // if `key` fits in `prefix` of `DefaultBranch`
3404 			{
3405 				next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, 1 + capacityIncrement, curr.key[0 .. $ - 1], // all but last
3406 															Leaf1!Value(makeFixedSizeNodeValue!(HeptLeaf1)(curr.key.back))); // last as a leaf
3407 			}
3408 			else				// curr.key.length > DefaultBranch.prefixCapacity + 1
3409 			{
3410 				next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, 1 + capacityIncrement, curr.key[0 .. DefaultBranch.prefixCapacity]);
3411 				next = insertNewAtBelowPrefix(alloc, next, curr.key[DefaultBranch.prefixCapacity .. $]);
3412 			}
3413 
3414 			freeNode(alloc, curr);
3415 			return next;
3416 		}
3417 
3418 		/** Destructively expand `curr` to make room for `capacityIncrement` more keys and return it. */
3419 		Branch expand(A)(auto ref A alloc, TwoLeaf3 curr, in size_t capacityIncrement = 1)
3420 			pure nothrow @safe @nogc
3421 		{
3422 			typeof(return) next;
3423 			if (curr.keys.length == 1) // only one key
3424 			{
3425 				next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, 1 + capacityIncrement);
3426 				next = insertNewAtAbovePrefix(alloc, next, // current keys plus one more
3427 											  curr.keys.at!0);
3428 			}
3429 			else
3430 			{
3431 				next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, curr.keys.length + capacityIncrement, curr.prefix);
3432 				/+ TODO: functionize and optimize to insertNewAtAbovePrefix(next, curr.keys) +/
3433 				foreach (key; curr.keys)
3434 					next = insertNewAtBelowPrefix(alloc, next, key[curr.prefix.length .. $]);
3435 			}
3436 			freeNode(alloc, curr);
3437 			return next;
3438 		}
3439 
3440 		/** Destructively expand `curr` to make room for `capacityIncrement` more keys and return it. */
3441 		Branch expand(A)(auto ref A alloc, TriLeaf2 curr, in size_t capacityIncrement = 1) pure nothrow @safe @nogc
3442 		{
3443 			typeof(return) next;
3444 			if (curr.keys.length == 1) // only one key
3445 			{
3446 				next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, 1 + capacityIncrement); // current keys plus one more
3447 				next = insertNewAtAbovePrefix(alloc, next, curr.keys.at!0);
3448 			}
3449 			else
3450 			{
3451 				next = makeVariableSizeNodePointer!(DefaultBranch)(alloc, curr.keys.length + capacityIncrement, curr.prefix);
3452 				/+ TODO: functionize and optimize to insertNewAtAbovePrefix(next, curr.keys) +/
3453 				foreach (key; curr.keys)
3454 					next = insertNewAtBelowPrefix(alloc, next, key[curr.prefix.length .. $]);
3455 			}
3456 			freeNode(alloc, curr);
3457 			return next;
3458 		}
3459 
3460 		/** Destructively expand `curr` making room for `nextKey` and return it. */
3461 		Node expand(A)(auto ref A alloc, HeptLeaf1 curr, in size_t capacityIncrement = 1)
3462 		{
3463 			auto next = makeVariableSizeNodePointer!(SparseLeaf1!Value)(alloc, curr.keys.length + capacityIncrement, curr.keys);
3464 			freeNode(alloc, curr);
3465 			return Node(next);
3466 		}
3467 	}
3468 
3469 	pure nothrow @safe @nogc
3470 	{
3471 		pragma(inline, true) void release(A)(auto ref A alloc, SparseLeaf1!Value* curr) { freeNode(alloc, curr); }
3472 		pragma(inline, true) void release(A)(auto ref A alloc, DenseLeaf1!Value* curr) { freeNode(alloc, curr); }
3473 
3474 		void release(A)(auto ref A alloc, SparseBranch* curr)
3475 		{
3476 			foreach (immutable sub; curr.subNodes[0 .. curr.subCount])
3477 				release(alloc, sub); // recurse branch
3478 			if (curr.leaf1)
3479 				release(alloc, curr.leaf1); // recurse leaf
3480 			freeNode(alloc, curr);
3481 		}
3482 
3483 		void release(A)(auto ref A alloc, DenseBranch* curr)
3484 		{
3485 			import std.algorithm.iteration : filter;
3486 			foreach (immutable sub; curr.subNodes[].filter!(sub => sub)) /+ TODO: use static foreach +/
3487 				release(alloc, sub); // recurse branch
3488 			if (curr.leaf1)
3489 				release(alloc, curr.leaf1); // recurse leaf
3490 			freeNode(alloc, curr);
3491 		}
3492 
3493 		pragma(inline, true) void release(A)(auto ref A alloc, OneLeafMax7 curr) { freeNode(alloc, curr); }
3494 		pragma(inline, true) void release(A)(auto ref A alloc, TwoLeaf3 curr) { freeNode(alloc, curr); }
3495 		pragma(inline, true) void release(A)(auto ref A alloc, TriLeaf2 curr) { freeNode(alloc, curr); }
3496 		pragma(inline, true) void release(A)(auto ref A alloc, HeptLeaf1 curr) { freeNode(alloc, curr); }
3497 
3498 		/** Release `Leaf1!Value curr`. */
3499 		void release(A)(auto ref A alloc, Leaf1!Value curr)
3500 		{
3501 			final switch (curr.typeIx) with (Leaf1!Value.Ix)
3502 			{
3503 			case undefined:
3504 				break; // ignored
3505 			case ix_HeptLeaf1: return release(alloc, curr.as!(HeptLeaf1));
3506 			case ix_SparseLeaf1Ptr: return release(alloc, curr.as!(SparseLeaf1!Value*));
3507 			case ix_DenseLeaf1Ptr: return release(alloc, curr.as!(DenseLeaf1!Value*));
3508 			}
3509 		}
3510 
3511 		/** Release `Node curr`. */
3512 		void release(A)(auto ref A alloc, Node curr)
3513 		{
3514 			final switch (curr.typeIx) with (Node.Ix)
3515 			{
3516 			case undefined:
3517 				break; // ignored
3518 			case ix_OneLeafMax7: return release(alloc, curr.as!(OneLeafMax7));
3519 			case ix_TwoLeaf3: return release(alloc, curr.as!(TwoLeaf3));
3520 			case ix_TriLeaf2: return release(alloc, curr.as!(TriLeaf2));
3521 			case ix_HeptLeaf1: return release(alloc, curr.as!(HeptLeaf1));
3522 			case ix_SparseLeaf1Ptr: return release(alloc, curr.as!(SparseLeaf1!Value*));
3523 			case ix_DenseLeaf1Ptr: return release(alloc, curr.as!(DenseLeaf1!Value*));
3524 			case ix_SparseBranchPtr: return release(alloc, curr.as!(SparseBranch*));
3525 			case ix_DenseBranchPtr: return release(alloc, curr.as!(DenseBranch*));
3526 			}
3527 		}
3528 
3529 		bool isHeapAllocatedNode(const Node curr)
3530 		{
3531 			final switch (curr.typeIx) with (Node.Ix)
3532 			{
3533 			case undefined:
3534 				return false;
3535 			case ix_OneLeafMax7: return false;
3536 			case ix_TwoLeaf3: return false;
3537 			case ix_TriLeaf2: return false;
3538 			case ix_HeptLeaf1: return false;
3539 			case ix_SparseLeaf1Ptr: return true;
3540 			case ix_DenseLeaf1Ptr: return true;
3541 			case ix_SparseBranchPtr: return true;
3542 			case ix_DenseBranchPtr: return true;
3543 			}
3544 		}
3545 	}
3546 
3547 	void printAt(Node curr, size_t depth, uint subIx = uint.max) @safe
3548 	{
3549 		import std.range : repeat;
3550 		import std.stdio : write, writeln;
3551 		import std.string : format;
3552 
3553 		if (!curr)
3554 			return;
3555 
3556 		foreach (immutable i; 0 .. depth)
3557 			write('-');		 // prefix
3558 
3559 		if (subIx != uint.max)
3560 			write(format("%.2X ", subIx));
3561 
3562 		final switch (curr.typeIx) with (Node.Ix)
3563 		{
3564 		case undefined:
3565 			assert(false, "Trying to print Node.undefined");
3566 		case ix_OneLeafMax7:
3567 			auto curr_ = curr.as!(OneLeafMax7);
3568 			import std.conv : to;
3569 			writeln(typeof(curr_).stringof, " #", curr_.key.length, ": ", curr_.to!string);
3570 			break;
3571 		case ix_TwoLeaf3:
3572 			auto curr_ = curr.as!(TwoLeaf3);
3573 			writeln(typeof(curr_).stringof, " #", curr_.keys.length, ": ", curr_.keys);
3574 			break;
3575 		case ix_TriLeaf2:
3576 			auto curr_ = curr.as!(TriLeaf2);
3577 			writeln(typeof(curr_).stringof, " #", curr_.keys.length, ": ", curr_.keys);
3578 			break;
3579 		case ix_HeptLeaf1:
3580 			auto curr_ = curr.as!(HeptLeaf1);
3581 			writeln(typeof(curr_).stringof, " #", curr_.keys.length, ": ", curr_.keys);
3582 			break;
3583 		case ix_SparseLeaf1Ptr:
3584 			auto curr_ = curr.as!(SparseLeaf1!Value*);
3585 			write(typeof(*curr_).stringof, " #", curr_.length, "/", curr_.capacity, " @", curr_);
3586 			write(": ");
3587 			bool other = false;
3588 			foreach (immutable i, immutable ix; curr_.ixs)
3589 			{
3590 				string s;
3591 				if (other)
3592 					s ~= keySeparator;
3593 				else
3594 					other = true;
3595 				import std.string : format;
3596 				s ~= format("%.2X", ix);
3597 				write(s);
3598 				static if (isValue)
3599 					write("=>", curr_.values[i]);
3600 			}
3601 			writeln();
3602 			break;
3603 		case ix_DenseLeaf1Ptr:
3604 			auto curr_ = curr.as!(DenseLeaf1!Value*);
3605 			write(typeof(*curr_).stringof, " #", curr_.count, " @", curr_);
3606 			write(": ");
3607 
3608 			// keys
3609 			size_t ix = 0;
3610 			bool other = false;
3611 			if (curr_._ixBits.allOne)
3612 				write("ALL");
3613 			else
3614 			{
3615 				foreach (immutable keyBit; curr_._ixBits[])
3616 				{
3617 					string s;
3618 					if (keyBit)
3619 					{
3620 						if (other)
3621 							s ~= keySeparator;
3622 						else
3623 							other = true;
3624 						import std.string : format;
3625 						s ~= format("%.2X", ix);
3626 					}
3627 					write(s);
3628 					static if (isValue)
3629 						write("=>", curr_.values[ix]);
3630 					++ix;
3631 				}
3632 			}
3633 
3634 			writeln();
3635 			break;
3636 		case ix_SparseBranchPtr:
3637 			auto curr_ = curr.as!(SparseBranch*);
3638 			write(typeof(*curr_).stringof, " #", curr_.subCount, "/", curr_.subCapacity, " @", curr_);
3639 			if (!curr_.prefix.empty)
3640 				write(" prefix=", curr_.prefix.toString('_'));
3641 			writeln(":");
3642 			if (curr_.leaf1)
3643 				printAt(Node(curr_.leaf1), depth + 1);
3644 			foreach (immutable i, immutable subNode; curr_.subNodes)
3645 				printAt(subNode, depth + 1, cast(uint)curr_.subIxs[i]);
3646 			break;
3647 		case ix_DenseBranchPtr:
3648 			auto curr_ = curr.as!(DenseBranch*);
3649 			write(typeof(*curr_).stringof, " #", curr_.subCount, "/", radix, " @", curr_);
3650 			if (!curr_.prefix.empty)
3651 				write(" prefix=", curr_.prefix.toString('_'));
3652 			writeln(":");
3653 			if (curr_.leaf1)
3654 				printAt(Node(curr_.leaf1), depth + 1);
3655 			foreach (immutable i, immutable subNode; curr_.subNodes)
3656 				printAt(subNode, depth + 1, cast(uint)i);
3657 
3658 			break;
3659 		}
3660 	}
3661 
3662 	struct RawRadixTree
3663 	{
3664 		import std.experimental.allocator.common : stateSize;
3665 		alias NodeType = Node;
3666 		alias BranchType = Branch;
3667 		alias DefaultBranchType = DefaultBranch;
3668 		alias ValueType = Value;
3669 		alias RangeType = Range;
3670 		alias StatsType = Stats;
3671 		alias SparseBranchType = SparseBranch;
3672 		alias DenseBranchType = DenseBranch;
3673 		alias ElementRefType = ElementRef;
3674 
3675 		/** Is `true` if this tree stores values of type `Value` along with keys. In
3676 			other words: `this` is a $(I map) rather than a $(I set).
3677 		*/
3678 		alias hasValue = isValue;
3679 
3680 		pragma(inline, true)
3681 		Range opSlice() pure nothrow => Range(_root, []); /+ TODO: DIP-1000 scope +/
3682 
3683 		/** Returns a duplicate of this tree if present.
3684 			Shallowly duplicates the values in the map case.
3685 		*/
3686 		typeof(this) dup() @trusted
3687 		{
3688 			static if (stateSize!A_ != 0)
3689 				return typeof(return)(treedup(_root, _alloc), length, _alloc);
3690 			else
3691 				return typeof(return)(treedup(_root, _alloc), length);
3692 		}
3693 
3694 		Stats usageHistograms() const
3695 		{
3696 			if (!_root)
3697 				return typeof(return).init;
3698 			typeof(return) stats;
3699 			_root.appendStatsTo!(Value, A_)(stats);
3700 			return stats;
3701 		}
3702 
3703 		static if (stateSize!A_ != 0)
3704 		{
3705 			this(A)(Node root, size_t length, auto ref A alloc) pure nothrow @safe @nogc
3706 			in(alloc !is null)
3707 			{
3708 				static if (is(typeof(alloc !is null)))
3709 					assert(alloc !is null);
3710 				this._root = root;
3711 				this._length = length;
3712 				this._alloc = alloc;
3713 			}
3714 
3715 			this(A)(auto ref A alloc) pure nothrow @safe @nogc
3716 			in(alloc !is null)
3717 			{
3718 				static if (is(typeof(alloc !is null)))
3719 					assert(alloc !is null);
3720 				this._alloc = alloc;
3721 			}
3722 
3723 			this() @disable; ///< No default construction if an allocator must be provided.
3724 		}
3725 		else
3726 		{
3727 			this(Node root, size_t length) pure nothrow @safe @nogc
3728 			{
3729 				static if (is(typeof(alloc !is null)))
3730 					assert(alloc !is null);
3731 				this._root = root;
3732 				this._length = length;
3733 			}
3734 		}
3735 
3736 		this(this) @disable;	// disable copy constructor
3737 
3738 		~this() nothrow
3739 		{
3740 			release(_alloc, _root);
3741 			debug _root = Node.init;
3742 		}
3743 
3744 		/** Removes all contents (elements). */
3745 		void clear()
3746 		{
3747 			release(_alloc, _root);
3748 			_root = null;
3749 			_length = 0;
3750 		}
3751 
3752 		void print() @safe const => printAt(_root, 0);
3753 
3754 		pure nothrow @safe @nogc:
3755 
3756 		/** Insert `key` into `this` tree. */
3757 		static if (isValue)
3758 			Node insert(UKey key, Value value, out ElementRef elementRef) => _root = insertAt(_alloc, _root, Elt!Value(key, value), elementRef);
3759 		else
3760 			Node insert(UKey key, out ElementRef elementRef) => _root = insertAt(_alloc, _root, key, elementRef);
3761 
3762 		pragma(inline, true):
3763 
3764 		Node root() @property => _root;
3765 
3766 		/** Returns: `true` if `key` is stored, `false` otherwise. */
3767 		inout(Node) prefix(UKey keyPrefix, out UKey keyPrefixRest) inout => prefixAt(_root, keyPrefix, keyPrefixRest);
3768 
3769 		/** Lookup deepest node having whose key starts with `key`. */
3770 		inout(Node) matchCommonPrefix(UKey key, out UKey keyRest) inout => matchCommonPrefixAt(_root, key, keyRest);
3771 
3772 		static if (isValue)
3773 			/** Returns: `true` if `key` is stored, `false` otherwise. */
3774 			inout(Value*) contains(UKey key) inout => containsAt(_root, key);
3775 		else
3776 			/** Returns: `true` if `key` is stored, `false` otherwise. */
3777 			bool contains(UKey key) const => containsAt(_root, key);
3778 
3779 		size_t countHeapNodes() => countHeapNodesAt(_root);
3780 
3781 		/** Returns: `true` iff tree is empty (no elements stored). */
3782 		bool empty() const @property => !_root;
3783 
3784 		/** Returns: number of elements stored. */
3785 		size_t length() const @property => _length;
3786 
3787 		Node rootNode() @property const => _root;
3788 
3789 	private:
3790 		/** Returns: number of nodes used in `this` tree. Should always equal `Stats.heapNodeCount`. */
3791 		// debug size_t heapAllocationBalance() { return _heapAllocBalance; }
3792 
3793 	private:
3794 		Node _root;
3795 		size_t _length; /// Number of elements (keys or key-value-pairs) currently stored under `_root`
3796 		static if (stateSize!A_ == 0)
3797 			alias _alloc = A_.instance;
3798 		else
3799 			A_ _alloc;
3800 	}
3801 }
3802 
3803 /** Append statistics of tree under `Node` `curr.` into `stats`.
3804  */
3805 static private void appendStatsTo(Value, A)(RawRadixTree!(Value, A).NodeType curr,
3806 													ref RawRadixTree!(Value, A).StatsType stats) // TODO make `StatsType` not depend on `A`
3807 	@safe pure nothrow /* TODO: @nogc */
3808 if (isAllocator!A)
3809 {
3810 	alias RT = RawRadixTree!(Value, A);
3811 	++stats.popByNodeType[curr.typeIx];
3812 
3813 	final switch (curr.typeIx) with (RT.NodeType.Ix)
3814 	{
3815 	case undefined:
3816 		break;
3817 	case ix_OneLeafMax7: break; /+ TODO: appendStatsTo() +/
3818 	case ix_TwoLeaf3: break; /+ TODO: appendStatsTo() +/
3819 	case ix_TriLeaf2: break; /+ TODO: appendStatsTo() +/
3820 	case ix_HeptLeaf1: break; /+ TODO: appendStatsTo() +/
3821 	case ix_SparseLeaf1Ptr:
3822 		++stats.heapNodeCount;
3823 		const curr_ = curr.as!(SparseLeaf1!Value*);
3824 		assert(curr_.length);
3825 		++stats.popHist_SparseLeaf1[curr_.length - 1]; /+ TODO: type-safe indexing +/
3826 		stats.sparseLeaf1AllocatedSizeSum += curr_.allocatedSize;
3827 		break;
3828 	case ix_DenseLeaf1Ptr:
3829 		const curr_ = curr.as!(DenseLeaf1!Value*);
3830 		++stats.heapNodeCount;
3831 		immutable count = curr_._ixBits.countOnes; // number of non-zero sub-nodes
3832 		assert(count <= curr_.capacity);
3833 		++stats.popHist_DenseLeaf1[count - 1]; /+ TODO: type-safe indexing +/
3834 		break;
3835 	case ix_SparseBranchPtr:
3836 		++stats.heapNodeCount;
3837 		curr.as!(RT.SparseBranchType*).appendStatsTo(stats);
3838 		break;
3839 	case ix_DenseBranchPtr:
3840 		++stats.heapNodeCount;
3841 		curr.as!(RT.DenseBranchType*).appendStatsTo(stats);
3842 		break;
3843 	}
3844 }
3845 
3846 /** Append statistics of tree under `Leaf1!Value` `curr.` into `stats`.
3847  */
3848 static private void appendStatsTo(Value, A)(Leaf1!Value curr, ref RawRadixTree!(Value, A).StatsType stats)
3849 pure nothrow @safe @nogc /* TODO: @nogc */
3850 if (isAllocator!A)
3851 {
3852 	alias RT = RawRadixTree!(Value, A);
3853 	++stats.popByLeaf1Type[curr.typeIx];
3854 
3855 	final switch (curr.typeIx) with (Leaf1!Value.Ix)
3856 	{
3857 	case undefined:
3858 		break;
3859 	case ix_HeptLeaf1: break; /+ TODO: appendStatsTo() +/
3860 	case ix_SparseLeaf1Ptr:
3861 		++stats.heapNodeCount;
3862 		const curr_ = curr.as!(SparseLeaf1!Value*);
3863 		assert(curr_.length);
3864 		++stats.popHist_SparseLeaf1[curr_.length - 1]; /+ TODO: type-safe indexing +/
3865 		break;
3866 	case ix_DenseLeaf1Ptr:
3867 		const curr_ = curr.as!(DenseLeaf1!Value*);
3868 		++stats.heapNodeCount;
3869 		immutable count = curr_._ixBits.countOnes; // number of non-zero curr-nodes
3870 		assert(count <= curr_.capacity);
3871 		assert(count);
3872 		++stats.popHist_DenseLeaf1[count - 1]; /+ TODO: type-safe indexing +/
3873 		break;
3874 	}
3875 }
3876 
3877 /** Remap fixed-length typed key `typedKey` to raw (untyped) key of type `UKey`.
3878 	TODO: DIP-1000 scope
3879 */
3880 UKey toFixedRawKey(TypedKey)(const scope TypedKey typedKey, UKey preallocatedFixedUKey) @trusted
3881 {
3882 	enum radix = 2^^span;	 // branch-multiplicity, typically either 2, 4, 16 or 256
3883 	immutable key_ = typedKey.bijectToUnsigned;
3884 
3885 	static assert(key_.sizeof == TypedKey.sizeof);
3886 
3887 	enum nbits = 8*key_.sizeof; // number of bits in key
3888 	enum chunkCount = nbits/span; // number of chunks in key_
3889 	static assert(chunkCount*span == nbits, "Bitsize of TypedKey must be a multiple of span:" ~ span.stringof);
3890 
3891 	// big-endian storage
3892 	foreach (immutable i; 0 .. chunkCount) // for each chunk index
3893 	{
3894 		immutable bitShift = (chunkCount - 1 - i)*span; // most significant bit chunk first (MSBCF)
3895 		preallocatedFixedUKey[i] = (key_ >> bitShift) & (radix - 1); // part of value which is also an index
3896 	}
3897 
3898 	return preallocatedFixedUKey[];
3899 }
3900 
3901 /** Remap typed key `typedKey` to raw (untyped) key of type `UKey`.
3902  */
3903 UKey toRawKey(TypedKey)(in TypedKey typedKey, ref Ixs rawUKey) @trusted
3904 if (isTrieableKeyType!TypedKey)
3905 {
3906 	import std.traits : isArray;
3907 	enum radix = 2^^span;	 // branch-multiplicity, typically either 2, 4, 16 or 256
3908 
3909 	import nxt.container.traits : isAddress;
3910 	static if (isAddress!TypedKey)
3911 		static assert(0, "Shift TypedKey " ~ TypedKey.stringof ~ " down by its alignment before returning");
3912 
3913 	static if (isFixedTrieableKeyType!TypedKey)
3914 	{
3915 		rawUKey.length = TypedKey.sizeof;
3916 		return typedKey.toFixedRawKey(rawUKey[]);
3917 	}
3918 	else static if (isArray!TypedKey)
3919 	{
3920 		alias EType = Unqual!(typeof(TypedKey.init[0]));
3921 		static if (is(EType == char)) /+ TODO: extend to support isTrieableKeyType!TypedKey +/
3922 		{
3923 			import std.string : representation;
3924 			const ubyte[] ukey = typedKey.representation; // lexical byte-order
3925 			return cast(Ix[])ukey;						/+ TODO: needed? +/
3926 		}
3927 		else static if (is(EType == wchar))
3928 		{
3929 			immutable ushort[] rKey = typedKey.representation; // lexical byte-order.
3930 			/+ TODO: MSByte-order of elements in rKey for ordered access and good branching performance +/
3931 			immutable ubyte[] ukey = (cast(const ubyte*)rKey.ptr)[0 .. rKey[0].sizeof * rKey.length]; /+ TODO: @trusted functionize. Reuse existing Phobos function? +/
3932 			return ukey;
3933 		}
3934 		else static if (is(EType == dchar))
3935 		{
3936 			immutable uint[] rKey = typedKey.representation; // lexical byte-order
3937 			/+ TODO: MSByte-order of elements in rKey for ordered access and good branching performance +/
3938 			immutable ubyte[] ukey = (cast(const ubyte*)rKey.ptr)[0 .. rKey[0].sizeof * rKey.length]; /+ TODO: @trusted functionize. Reuse existing Phobos function? +/
3939 			return ukey;
3940 		}
3941 		else static if (isFixedTrieableKeyType!E)
3942 		{
3943 			static assert(false, "TODO: Convert array of typed fixed keys");
3944 		}
3945 		else
3946 		{
3947 			static assert(false, "TODO: Handle typed key " ~ TypedKey.stringof);
3948 		}
3949 	}
3950 	else static if (is(TypedKey == struct))
3951 	{
3952 		static if (TypedKey.tupleof.length == 1) // TypedKey is a wrapper type
3953 		{
3954 			return typedKey.tupleof[0].toRawKey(rawUKey);
3955 		}
3956 		else
3957 		{
3958 			enum members = __traits(allMembers, TypedKey);
3959 			foreach (immutable i, immutable memberName; members) // for each member name in `struct TypedKey`
3960 			{
3961 				immutable member = __traits(getMember, typedKey, memberName); // member
3962 				alias MemberType = typeof(member);
3963 
3964 				static if (i + 1 == members.length) // last member is allowed to be an array of fixed length
3965 				{
3966 					Ixs memberRawUKey;
3967 					const memberRawKey = member.toRawKey(memberRawUKey); /+ TODO: DIP-1000 scope +/
3968 					rawUKey ~= memberRawUKey;
3969 				}
3970 				else				// non-last member must be fixed
3971 				{
3972 					static assert(isFixedTrieableKeyType!MemberType,
3973 								  "Non-last " ~ i.stringof ~ ":th member of type " ~ MemberType.stringof ~ " must be of fixed length");
3974 					Ix[MemberType.sizeof] memberRawUKey;
3975 					const memberRawKey = member.toFixedRawKey(memberRawUKey); /+ TODO: DIP-1000 scope +/
3976 					rawUKey ~= memberRawUKey[];
3977 				}
3978 			}
3979 			return rawUKey[]; /+ TODO: return immutable slice +/
3980 		}
3981 	}
3982 	else
3983 	{
3984 		static assert(false, "TODO: Handle typed key " ~ TypedKey.stringof);
3985 	}
3986 }
3987 
3988 /** Remap raw untyped key `ukey` to typed key of type `TypedKey`. */
3989 inout(TypedKey) toTypedKey(TypedKey)(inout(Ix)[] ukey) @trusted
3990 if (isTrieableKeyType!TypedKey)
3991 {
3992 	import std.traits : isArray;
3993 	import nxt.container.traits : isAddress;
3994 	static if (isAddress!TypedKey)
3995 		static assert(0, "Shift TypedKey " ~ TypedKey.stringof ~ " up by its alignment before returning");
3996 
3997 	static if (isFixedTrieableKeyType!TypedKey)
3998 	{
3999 		enum nbits = 8*TypedKey.sizeof; // number of bits in key
4000 		enum chunkCount = nbits/span; // number of chunks in key_
4001 		static assert(chunkCount*span == nbits, "Bitsize of TypedKey must be a multiple of span:" ~ span.stringof);
4002 
4003 		/+ TODO: reuse existing trait UnsignedOf!TypedKey +/
4004 		static	  if (TypedKey.sizeof == 1) { alias RawKey = ubyte; }
4005 		else static if (TypedKey.sizeof == 2) { alias RawKey = ushort; }
4006 		else static if (TypedKey.sizeof == 4) { alias RawKey = uint; }
4007 		else static if (TypedKey.sizeof == 8) { alias RawKey = ulong; }
4008 
4009 		RawKey bKey = 0;
4010 
4011 		// big-endian storage
4012 		foreach (immutable i; 0 .. chunkCount) // for each chunk index
4013 		{
4014 			immutable RawKey uix = cast(RawKey)ukey[i];
4015 			immutable bitShift = (chunkCount - 1 - i)*span; // most significant bit chunk first (MSBCF)
4016 			bKey |= uix << bitShift; // part of value which is also an index
4017 		}
4018 
4019 		TypedKey typedKey;
4020 		bKey.bijectFromUnsigned(typedKey);
4021 		return typedKey;
4022 	}
4023 	else static if (isArray!TypedKey)
4024 	{
4025 		static if (isArray!TypedKey &&
4026 				   is(Unqual!(typeof(TypedKey.init[0])) == char))
4027 		{
4028 			static assert(char.sizeof == Ix.sizeof);
4029 			return cast(inout(char)[])ukey;
4030 		}
4031 		/+ TODO: handle wchar and dchar +/
4032 		else
4033 		{
4034 			static assert(false, "TODO: Handle typed key " ~ TypedKey.stringof);
4035 		}
4036 	}
4037 	else static if (is(TypedKey == struct))
4038 	{
4039 		static if (TypedKey.tupleof.length == 1) // TypedKey is a wrapper type
4040 		{
4041 			alias WrappedTypedKey = typeof(TypedKey.tupleof[0]);
4042 			return TypedKey(ukey.toTypedKey!(WrappedTypedKey));
4043 		}
4044 		else
4045 		{
4046 			TypedKey typedKey;
4047 			size_t ix = 0;
4048 			enum members = __traits(allMembers, TypedKey);
4049 			foreach (immutable i, immutable memberName; members) // for each member name in `struct TypedKey`
4050 			{
4051 				alias MemberType = typeof(__traits(getMember, typedKey, memberName));
4052 				static if (i + 1 != members.length) // last member is allowed to be an array of fixed length
4053 					static assert(isFixedTrieableKeyType!MemberType,
4054 								  "Non-last MemberType must be fixed length");
4055 				__traits(getMember, typedKey, memberName) = ukey[ix .. ix + MemberType.sizeof].toTypedKey!MemberType;
4056 				ix += MemberType.sizeof;
4057 			}
4058 			return typedKey;
4059 		}
4060 	}
4061 	else
4062 	{
4063 		static assert(false, "TODO: Handle typed key " ~ TypedKey.stringof);
4064 	}
4065 }
4066 
4067 /** Radix-Tree with key of type `K` and value of type `V` (if non-`void`).
4068  *
4069  * Radix-tree is also called a patricia trie, radix trie or compact prefix tree.
4070  */
4071 struct RadixTree(K, V, A = Mallocator)
4072 if (isTrieableKeyType!(K) &&
4073 	isAllocator!A)
4074 {
4075 	import std.experimental.allocator.common : stateSize;
4076 
4077 	// pragma(msg, __FILE__, "(", __LINE__, ",1): Debug: ", A);
4078 	alias KeyType = K;
4079 
4080 	/** Keys are stored in a way that they can't be accessed by reference so we
4081 		allow array (and string) keys to be of mutable type.
4082 	*/
4083 	static if (is(K == U[], U)) // isDynamicArray
4084 		alias MK = const(Unqual!(U))[];
4085 	else
4086 		alias MK = K;
4087 
4088 	static if (stateSize!A != 0)
4089 	{
4090 		this() @disable; ///< No default construction if an allocator must be provided.
4091 
4092 		/** Use the given `allocator` for allocations. */
4093 		this(A alloc)
4094 		in(alloc !is null)
4095 		do
4096 		{
4097 			this._alloc = alloc;
4098 		}
4099 	}
4100 
4101 	private alias RawTree = RawRadixTree!(V, A);
4102 
4103 	static if (RawTree.hasValue)
4104 	{
4105 		alias ValueType = V;
4106 
4107 		/** Element type. */
4108 		struct E
4109 		{
4110 			K key;
4111 			V value;
4112 		}
4113 		alias ElementType = E;
4114 
4115 		ref V opIndex(const scope MK key)
4116 		{
4117 			pragma(inline, true);
4118 			V* value = contains(key);
4119 			version (assert)
4120 			{
4121 				import core.exception : RangeError;
4122 				if (value is null)
4123 					throw new RangeError("Range violation");
4124 			}
4125 			return *value;
4126 		}
4127 
4128 		auto opIndexAssign(V value, const scope MK key)
4129 		{
4130 			_rawTree.ElementRefType elementRef; // reference to where element was added
4131 
4132 			Ixs rawUKey;
4133 			auto rawKey = key.toRawKey(rawUKey); /+ TODO: DIP-1000 scope +/
4134 
4135 			_rawTree.insert(rawKey, value, elementRef);
4136 
4137 			immutable bool added = elementRef.node && elementRef.modStatus == ModStatus.added;
4138 			_length += added;
4139 			/* TODO: return reference (via `auto ref` return typed) to stored
4140 			   value at `elementRef` instead, unless packed static_bitarray storage is used
4141 			   when `V is bool` */
4142 			return value;
4143 		}
4144 
4145 		/** Insert element `elt`.
4146 		 * Returns: `true` if `elt.key` wasn't previously added, `false` otherwise.
4147 		 */
4148 		bool insert(in ElementType e) @nogc => insert(e.key, e.value);
4149 
4150 		/** Insert `key` with `value`.
4151 			Returns: `true` if `key` wasn't previously added, `false` otherwise.
4152 		*/
4153 		bool insert(const scope MK key, V value) @nogc
4154 		{
4155 			_rawTree.ElementRefType elementRef; // indicates that key was added
4156 
4157 			Ixs rawUKey;
4158 			auto rawKey = key.toRawKey(rawUKey); /+ TODO: DIP-1000 scope +/
4159 
4160 			_rawTree.insert(rawKey, value, elementRef);
4161 
4162 			// debug if (willFail) { dbg("WILL FAIL: elementRef:", elementRef, " key:", key); }
4163 			if (elementRef.node)  // if `key` was added at `elementRef`
4164 			{
4165 				// set value
4166 				final switch (elementRef.node.typeIx) with (_rawTree.NodeType.Ix)
4167 				{
4168 				case undefined:
4169 				case ix_OneLeafMax7:
4170 				case ix_TwoLeaf3:
4171 				case ix_TriLeaf2:
4172 				case ix_HeptLeaf1:
4173 				case ix_SparseBranchPtr:
4174 				case ix_DenseBranchPtr:
4175 					assert(false);
4176 				case ix_SparseLeaf1Ptr:
4177 					elementRef.node.as!(SparseLeaf1!V*).setValue(UIx(rawKey[$ - 1]), value);
4178 					break;
4179 				case ix_DenseLeaf1Ptr:
4180 					elementRef.node.as!(DenseLeaf1!V*).setValue(UIx(rawKey[$ - 1]), value);
4181 					break;
4182 				}
4183 				immutable bool hit = elementRef.modStatus == ModStatus.added;
4184 				_length += hit;
4185 				return hit;
4186 			}
4187 			else
4188 				assert(false, "TODO: warning no elementRef for key:"/*, key, " rawKey:", rawKey*/);
4189 		}
4190 
4191 		/** Returns: pointer to value if `key` is contained in set, null otherwise. */
4192 		inout(V*) contains(const scope MK key) inout @nogc
4193 		{
4194 			version (LDC) pragma(inline, true);
4195 			Ixs rawUKey;
4196 			auto rawKey = key.toRawKey(rawUKey); /+ TODO: DIP-1000 scope +/
4197 			return _rawTree.contains(rawKey);
4198 		}
4199 
4200 		/** AA-style key-value range. */
4201 		pragma(inline, true)
4202 		Range byKeyValue() @nogc => this.opSlice; /+ TODO: inout?, TODO: DIP-1000 scope +/
4203 	}
4204 	else
4205 	{
4206 		alias ElementType = K;
4207 
4208 		/** Insert `key`.
4209 			Returns: `true` if `key` wasn't previously added, `false` otherwise.
4210 		*/
4211 		bool insert(const scope MK key) @nogc
4212 		{
4213 			_rawTree.ElementRefType elementRef; // indicates that elt was added
4214 
4215 			Ixs rawUKey;
4216 			auto rawKey = key.toRawKey(rawUKey); /+ TODO: DIP-1000 scope +/
4217 
4218 			_rawTree.insert(rawKey, elementRef);
4219 
4220 			immutable bool hit = elementRef.node && elementRef.modStatus == ModStatus.added;
4221 			_length += hit;
4222 			return hit;
4223 		}
4224 
4225 		/** Returns: `true` if `key` is stored, `false` otherwise. */
4226 		bool contains(const scope MK key) inout nothrow @nogc
4227 		{
4228 			version (LDC) pragma(inline, true);
4229 			Ixs rawUKey;
4230 			auto rawKey = key.toRawKey(rawUKey); /+ TODO: DIP-1000 scope +/
4231 			return _rawTree.contains(rawKey);
4232 		}
4233 
4234 		/** AA-style key range. */
4235 		pragma(inline, true)
4236 		Range byKey() nothrow @nogc => this.opSlice; /+ TODO: inout?. TODO: DIP-1000 scope +/
4237 	}
4238 
4239 	/** Supports $(B `K` in `this`) syntax. */
4240 	auto opBinaryRight(string op)(const scope MK key) inout if (op == "in")
4241 	{
4242 		pragma(inline, true);
4243 		return contains(key);   /+ TODO: return `_rawTree.ElementRefType` +/
4244 	}
4245 
4246 	Range opSlice() @system @nogc /+ TODO: inout? +/
4247 	{
4248 		version (LDC) pragma(inline, true);
4249 		return Range(_root, []);
4250 	}
4251 
4252 	/** Get range over elements whose key starts with `keyPrefix`.
4253 		The element equal to `keyPrefix` is return as an empty instance of the type.
4254 	 */
4255 	auto prefix(const scope MK keyPrefix) @system
4256 	{
4257 		Ixs rawUKey;
4258 		auto rawKeyPrefix = keyPrefix.toRawKey(rawUKey);
4259 
4260 		UKey rawKeyPrefixRest;
4261 		auto prefixedRootNode = _rawTree.prefix(rawKeyPrefix, rawKeyPrefixRest);
4262 
4263 		return Range(prefixedRootNode,
4264 					 rawKeyPrefixRest);
4265 	}
4266 
4267 	/** Typed Range. */
4268 	private static struct Range
4269 	{
4270 		this(RawTree.NodeType root, UKey keyPrefixRest) @nogc
4271 		{
4272 			pragma(inline, true);
4273 			_rawRange = _rawTree.RangeType(root, keyPrefixRest);
4274 		}
4275 
4276 		ElementType front() const @nogc
4277 		{
4278 			pragma(inline, true);
4279 			static if (RawTree.hasValue)
4280 				return typeof(return)(_rawRange.lowKey.toTypedKey!K,
4281 									  _rawRange._front._cachedFrontValue);
4282 			else
4283 				return _rawRange.lowKey.toTypedKey!K;
4284 		}
4285 
4286 		ElementType back() const @nogc
4287 		{
4288 			pragma(inline, true);
4289 			static if (RawTree.hasValue)
4290 				return typeof(return)(_rawRange.highKey.toTypedKey!K,
4291 									  _rawRange._back._cachedFrontValue);
4292 			else
4293 				return _rawRange.highKey.toTypedKey!K;
4294 		}
4295 
4296 		@property typeof(this) save() @nogc
4297 		{
4298 			version (LDC) pragma(inline, true);
4299 			typeof(return) copy;
4300 			copy._rawRange = this._rawRange.save;
4301 			return copy;
4302 		}
4303 
4304 		RawTree.RangeType _rawRange;
4305 		alias _rawRange this;
4306 	}
4307 
4308 	/** Raw Range. */
4309 	private static struct RawRange
4310 	{
4311 		this(RawTree.NodeType root,
4312 			 UKey keyPrefixRest)
4313 		{
4314 			pragma(inline, true);
4315 			this._rawRange = _rawTree.RangeType(root, keyPrefixRest);
4316 		}
4317 
4318 		static if (RawTree.hasValue)
4319 		{
4320 			pragma(inline, true):
4321 			const(Elt!V) front() const => typeof(return)(_rawRange.lowKey,
4322 														 _rawRange._front._cachedFrontValue);
4323 			const(Elt!V) back() const => typeof(return)(_rawRange.highKey,
4324 														_rawRange._back._cachedFrontValue);
4325 		}
4326 		else
4327 		{
4328 			pragma(inline, true):
4329 			const(Elt!V) front() const => _rawRange.lowKey;
4330 			const(Elt!V) back() const => _rawRange.highKey;
4331 		}
4332 
4333 		@property typeof(this) save()
4334 		{
4335 			typeof(return) copy;
4336 			copy._rawRange = this._rawRange.save;
4337 			return copy;
4338 		}
4339 
4340 		RawTree.RangeType _rawRange;
4341 		alias _rawRange this;
4342 	}
4343 
4344 	/** This function searches with policy `sp` to find the largest right subrange
4345 		on which pred(value, x) is true for all x (e.g., if pred is "less than",
4346 		returns the portion of the range with elements strictly greater than
4347 		value).
4348 
4349 		TODO: Add template param (SearchPolicy sp)
4350 
4351 		TODO: replace `matchCommonPrefix` with something more clever directly
4352 		finds the next element after rawKey and returns a TreeRange at that point
4353 	*/
4354 	auto upperBound(const scope MK key) @system
4355 	{
4356 		Ixs rawUKey;
4357 		auto rawKey = key.toRawKey(rawUKey);
4358 		UKey rawKeyRest;
4359 		auto prefixedRootNode = _rawTree.matchCommonPrefix(rawKey, rawKeyRest);
4360 		return UpperBoundRange(prefixedRootNode,
4361 							   rawKey[0 .. $ - rawKeyRest.length],
4362 							   rawKeyRest);
4363 	}
4364 
4365 	/** Typed Upper Bound Range.
4366 	 */
4367 	private static struct UpperBoundRange
4368 	{
4369 		this(RawTree.NodeType root,
4370 			 UKey rawKeyPrefix, UKey rawKeyRest) @nogc
4371 		{
4372 			_rawRange = _rawTree.RangeType(root, []);
4373 			_rawKeyPrefix = rawKeyPrefix;
4374 
4375 			// skip values
4376 			import std.algorithm.comparison : cmp;
4377 			while (!_rawRange.empty &&
4378 				   cmp(_rawRange._front.frontKey, rawKeyRest) <= 0)
4379 			{
4380 				_rawRange.popFront();
4381 			}
4382 		}
4383 
4384 		ElementType front() @nogc
4385 		{
4386 			Ixs wholeRawKey = _rawKeyPrefix.dupShallow;
4387 			wholeRawKey ~= _rawRange.lowKey;
4388 			static if (RawTree.hasValue)
4389 				return typeof(return)(wholeRawKey[].toTypedKey!K,
4390 									  _rawRange._front._cachedFrontValue);
4391 			else
4392 				return wholeRawKey[].toTypedKey!K;
4393 		}
4394 
4395 		@property typeof(this) save() @nogc
4396 		{
4397 			typeof(return) copy;
4398 			copy._rawRange = this._rawRange.save;
4399 			copy._rawKeyPrefix = this._rawKeyPrefix.dupShallow;
4400 			return copy;
4401 		}
4402 
4403 		RawTree.RangeType _rawRange;
4404 		alias _rawRange this;
4405 		Ixs _rawKeyPrefix;
4406 	}
4407 
4408 	/** Returns a duplicate of this tree.
4409 		Shallowly duplicates the values in the map case.
4410 	*/
4411 	typeof(this) dup() => typeof(this)(this._rawTree.dup);
4412 
4413 	private RawTree _rawTree;
4414 	alias _rawTree this;
4415 }
4416 alias RadixTreeMap = RadixTree;
4417 
4418 template RadixTreeSet(K, A = Mallocator)
4419 if (isTrieableKeyType!(K) &&
4420 	isAllocator!A)
4421 {
4422 	alias RadixTreeSet = RadixTree!(K, void, A);
4423 }
4424 
4425 /** Print `tree`. */
4426 void print(Key, Value, A)(const ref RadixTree!(Key, Value, A) tree) @safe
4427 if (isTrieableKeyType!(K) &&
4428 	isAllocator!A)
4429 {
4430 	print(tree._rawTree);
4431 }
4432 
4433 pure nothrow @safe @nogc unittest
4434 { version (showAssertTags) dbg();
4435 	version (enterSingleInfiniteMemoryLeakTest)
4436 	{
4437 		while (true)
4438 		{
4439 			checkNumeric!(bool, float, double,
4440 						  long, int, short, byte,
4441 						  ulong, uint, ushort, ubyte);
4442 		}
4443 	}
4444 	else
4445 	{
4446 		checkNumeric!(bool, float, double,
4447 					  long, int, short, byte,
4448 					  ulong, uint, ushort, ubyte);
4449 	}
4450 }
4451 
4452 /// exercise all switch-cases in `RawRadixTree.prefixAt()`
4453 /*TODO: @safe*/ pure nothrow
4454 /*TODO:@nogc*/ unittest
4455 { version (showAssertTags) dbg();
4456 	import nxt.algorithm.comparison : equal;
4457 	alias Key = string;
4458 	auto set = RadixTreeSet!(Key, TestAllocator)();
4459 
4460 	set.clear();
4461 	set.insert(`-----1`);
4462 	assert(set.prefix(`-----`).equal([`1`]));
4463 	assert(set.prefix(`-----_`).empty);
4464 	assert(set.prefix(`-----____`).empty);
4465 	set.insert(`-----2`);
4466 	assert(set.prefix(`-----`).equal([`1`, `2`]));
4467 	assert(set.prefix(`-----_`).empty);
4468 	assert(set.prefix(`-----____`).empty);
4469 	set.insert(`-----3`);
4470 	assert(set.prefix(`-----`).equal([`1`, `2`, `3`]));
4471 	assert(set.prefix(`-----_`).empty);
4472 	assert(set.prefix(`-----____`).empty);
4473 	set.insert(`-----4`);
4474 	assert(set.prefix(`-----`).equal([`1`, `2`, `3`, `4`]));
4475 	assert(set.prefix(`-----_`).empty);
4476 	assert(set.prefix(`-----____`).empty);
4477 	set.insert(`-----5`);
4478 	assert(set.prefix(`-----`).equal([`1`, `2`, `3`, `4`, `5`]));
4479 	assert(set.prefix(`-----_`).empty);
4480 	assert(set.prefix(`-----____`).empty);
4481 	set.insert(`-----6`);
4482 	assert(set.prefix(`-----`).equal([`1`, `2`, `3`, `4`, `5`, `6`]));
4483 	assert(set.prefix(`-----_`).empty);
4484 	assert(set.prefix(`-----____`).empty);
4485 	set.insert(`-----7`);
4486 	assert(set.prefix(`-----`).equal([`1`, `2`, `3`, `4`, `5`, `6`, `7`]));
4487 	assert(set.prefix(`-----_`).empty);
4488 	assert(set.prefix(`-----____`).empty);
4489 	set.insert(`-----8`);
4490 	assert(set.prefix(`-----`).equal([`1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`]));
4491 	assert(set.prefix(`-----_`).empty);
4492 	assert(set.prefix(`-----____`).empty);
4493 	set.insert(`-----11`);
4494 	assert(set.prefix(`-----`).equal([`1`, `11`, `2`, `3`, `4`, `5`, `6`, `7`, `8`]));
4495 	set.insert(`-----22`);
4496 	assert(set.prefix(`-----`).equal([`1`, `11`, `2`, `22`, `3`, `4`, `5`, `6`, `7`, `8`]));
4497 	set.insert(`-----33`);
4498 	assert(set.prefix(`-----`).equal([`1`, `11`, `2`, `22`, `3`, `33`, `4`, `5`, `6`, `7`, `8`]));
4499 	set.insert(`-----44`);
4500 	assert(set.prefix(`-----`).equal([`1`, `11`, `2`, `22`, `3`, `33`, `4`, `44`, `5`, `6`, `7`, `8`]));
4501 
4502 	set.clear();
4503 	set.insert(`-----11`);
4504 	assert(set.prefix(`-----`).equal([`11`]));
4505 	set.insert(`-----22`);
4506 	assert(set.prefix(`-----`).equal([`11`, `22`]));
4507 	assert(set.prefix(`-----_`).empty);
4508 	assert(set.prefix(`-----___`).empty);
4509 
4510 	set.clear();
4511 	set.insert(`-----111`);
4512 	assert(set.prefix(`-----`).equal([`111`]));
4513 	set.insert(`-----122`);
4514 	assert(set.prefix(`-----`).equal([`111`, `122`]));
4515 	set.insert(`-----133`);
4516 	assert(set.prefix(`-----`).equal([`111`, `122`, `133`]));
4517 	assert(set.prefix(`-----1`).equal([`11`, `22`, `33`]));
4518 	assert(set.prefix(`-----1_`).empty);
4519 	assert(set.prefix(`-----1___`).empty);
4520 
4521 	set.clear();
4522 	set.insert(`-----1111`);
4523 	assert(set.prefix(`-----`).equal([`1111`]));
4524 	assert(set.prefix(`-----_`).empty);
4525 	assert(set.prefix(`-----___`).empty);
4526 
4527 	set.clear();
4528 	set.insert(`-----11111`);
4529 	assert(set.prefix(`-----`).equal([`11111`]));
4530 	assert(set.prefix(`-----_`).empty);
4531 	assert(set.prefix(`-----___`).empty);
4532 	set.insert(`-----12222`);
4533 	assert(set.prefix(`-----`).equal([`11111`, `12222`]));
4534 	assert(set.prefix(`-----_`).empty);
4535 	assert(set.prefix(`-----___`).empty);
4536 	assert(set.prefix(`-----12`).equal([`222`]));
4537 	assert(set.prefix(`-----12_`).empty);
4538 	assert(set.prefix(`-----12____`).empty);
4539 }
4540 
4541 /// test floating-point key range sortedness
4542 /*@ TODO: safe */ pure nothrow @nogc unittest
4543 { version (showAssertTags) dbg();
4544 	alias T = double;
4545 
4546 	auto set = RadixTreeSet!(T, TestAllocator)();
4547 
4548 	import std.range: isForwardRange;
4549 	static assert(isForwardRange!(typeof(set[])));
4550 
4551 	set.insert(-1.1e6);
4552 	set.insert(-2.2e9);
4553 	set.insert(-1.1);
4554 	set.insert(+2.2);
4555 	set.insert(T.max);
4556 	set.insert(-T.max);
4557 	set.insert(-3.3);
4558 	set.insert(-4.4);
4559 	set.insert(+4.4);
4560 	set.insert(+3.3);
4561 	set.insert(+1.1e6);
4562 	set.insert(+2.2e9);
4563 
4564 	import std.algorithm.sorting : isSorted;
4565 	assert(set[].isSorted);
4566 }
4567 
4568 version (unittest)
4569 auto testScalar(uint span, Keys...)() @safe
4570 if (Keys.length != 0)
4571 {
4572 	import std.algorithm.comparison : min, max;
4573 	import std.traits : isIntegral, isFloatingPoint;
4574 	import std.range : iota;
4575 	foreach (Key; Keys)
4576 	{
4577 		auto set = RadixTreeSet!(Key, TestAllocator)();
4578 
4579 		static if (isIntegral!Key)
4580 		{
4581 			immutable low = max(Key.min, -1_000);
4582 			immutable high = min(Key.max, 1_000);
4583 			immutable factor = 1;
4584 		}
4585 		else static if (isFloatingPoint!Key)
4586 		{
4587 			immutable low = -1_000;
4588 			immutable high = 1_000;
4589 			immutable factor = 1;
4590 		}
4591 		else static if (is(Key == bool))
4592 		{
4593 			immutable low = false;
4594 			immutable high = true;
4595 			immutable factor = 1;
4596 		}
4597 
4598 		foreach (immutable uk_; low.iota(high + 1))
4599 		{
4600 			immutable uk = factor*uk_;
4601 			immutable Key key = cast(Key)uk;
4602 			assert(set.insert(key));  // insert new value returns `true` (previously not in set)
4603 			assert(!set.insert(key)); // reinsert same value returns `false` (already in set)
4604 		}
4605 
4606 		import nxt.algorithm.comparison : equal;
4607 		import std.algorithm.iteration : map;
4608 		() @trusted { assert(set[].equal(low.iota(high + 1).map!(uk => cast(Key)uk))); } (); /+ TODO: remove @trusted when opSlice support DIP-1000 +/
4609 
4610 		import std.algorithm.sorting : isSorted;
4611 		() @trusted { assert(set[].isSorted); } (); /+ TODO: remove @trusted when opSlice support DIP-1000 +/
4612 	}
4613 }
4614 
4615 ///
4616 pure nothrow @safe @nogc unittest
4617 { version (showAssertTags) dbg();
4618 	testScalar!(8,
4619 				bool,
4620 				double, float,
4621 				long, int, short, byte,
4622 				ulong, uint, ushort, ubyte);
4623 }
4624 
4625 ///
4626 pure nothrow @safe @nogc unittest
4627 { version (showAssertTags) dbg();
4628 	alias Key = ubyte;
4629 	auto set = RadixTreeSet!(Key, TestAllocator)();
4630 
4631 	assert(!set.root);
4632 
4633 	foreach (immutable i; 0 .. 7)
4634 	{
4635 		assert(!set.contains(i));
4636 		assert(set.insert(i));
4637 		assert(set.root.peek!HeptLeaf1);
4638 	}
4639 
4640 	foreach (immutable i; 7 .. 48)
4641 	{
4642 		assert(!set.contains(i));
4643 		assert(set.insert(i));
4644 		assert(set.root.peek!(SparseLeaf1!void*));
4645 	}
4646 
4647 	foreach (immutable i; 48 .. 256)
4648 	{
4649 		assert(!set.contains(i));
4650 		assert(set.insert(i));
4651 		assert(set.root.peek!(DenseLeaf1!void*));
4652 	}
4653 }
4654 
4655 /** Calculate and print statistics of `tree`. */
4656 void showStatistics(RT)(const ref RT tree) // why does `in`RT tree` trigger a copy ctor here
4657 {
4658 	import std.conv : to;
4659 	import std.stdio : writeln;
4660 
4661 	auto stats = tree.usageHistograms;
4662 
4663 	writeln("Population By Node Type: ", stats.popByNodeType);
4664 	writeln("Population By Leaf1 Type: ", stats.popByLeaf1Type);
4665 
4666 	writeln("SparseBranch Population Histogram: ", stats.popHist_SparseBranch);
4667 	writeln("DenseBranch Population Histogram: ", stats.popHist_DenseBranch);
4668 
4669 	writeln("SparseLeaf1 Population Histogram: ", stats.popHist_SparseLeaf1);
4670 	writeln("DenseLeaf1 Population Histogram: ", stats.popHist_DenseLeaf1);
4671 
4672 	size_t totalBytesUsed = 0;
4673 
4674 	// Node-usage
4675 	foreach (const index, const pop; stats.popByNodeType) /+ TODO: use stats.byPair when added to typecons_ex.d +/
4676 	{
4677 		size_t bytesUsed = 0;
4678 		const ix = cast(RT.NodeType.Ix)index;
4679 		final switch (ix) with (RT.NodeType.Ix)
4680 		{
4681 		case undefined:
4682 			continue; // ignore
4683 		case ix_OneLeafMax7:
4684 			bytesUsed = pop*OneLeafMax7.sizeof;
4685 			break;
4686 		case ix_TwoLeaf3:
4687 			bytesUsed = pop*TwoLeaf3.sizeof;
4688 			break;
4689 		case ix_TriLeaf2:
4690 			bytesUsed = pop*TriLeaf2.sizeof;
4691 			break;
4692 		case ix_HeptLeaf1:
4693 			bytesUsed = pop*HeptLeaf1.sizeof;
4694 			break;
4695 		case ix_SparseLeaf1Ptr:
4696 			bytesUsed = stats.sparseLeaf1AllocatedSizeSum; // variable length struct
4697 			totalBytesUsed += bytesUsed;
4698 			break;
4699 		case ix_DenseLeaf1Ptr:
4700 			bytesUsed = pop*DenseLeaf1!(RT.ValueType).sizeof;
4701 			totalBytesUsed += bytesUsed;
4702 			break;
4703 		case ix_SparseBranchPtr:
4704 			bytesUsed = stats.sparseBranchAllocatedSizeSum; // variable length struct
4705 			totalBytesUsed += bytesUsed;
4706 			break;
4707 		case ix_DenseBranchPtr:
4708 			bytesUsed = pop*RT.DenseBranchType.sizeof;
4709 			totalBytesUsed += bytesUsed;
4710 			break;
4711 		}
4712 		writeln(pop, " number of ",
4713 				ix.to!string[3 .. $], /+ TODO: Use RT.NodeType.indexTypeName(ix) +/
4714 				" uses ", bytesUsed/1e6, " megabytes");
4715 	}
4716 
4717 	writeln("Tree uses ", totalBytesUsed/1e6, " megabytes");
4718 }
4719 
4720 /// test map from `uint` to values of type `double`
4721 pure nothrow @safe @nogc unittest
4722 { version (showAssertTags) dbg();
4723 	alias Key = uint;
4724 	alias Value = uint;
4725 
4726 	auto map = RadixTreeMap!(Key, Value, TestAllocator)();
4727 	assert(map.empty);
4728 
4729 	static assert(map.hasValue);
4730 
4731 	static Value keyToValue(Key key) pure nothrow @safe @nogc { return cast(Value)((key + 1)*radix); }
4732 
4733 	foreach (immutable i; 0 .. SparseLeaf1!Value.maxCapacity)
4734 	{
4735 		assert(!map.contains(i));
4736 		assert(map.length == i);
4737 		map[i] = keyToValue(i);
4738 		assert(map.contains(i));
4739 		assert(*map.contains(i) == keyToValue(i));
4740 		assert(i in map);
4741 		assert(*(i in map) == keyToValue(i));
4742 		assert(map.length == i + 1);
4743 	}
4744 
4745 	foreach (immutable i; SparseLeaf1!Value.maxCapacity .. radix)
4746 	{
4747 		assert(!map.contains(i));
4748 		assert(map.length == i);
4749 		map[i] = keyToValue(i);
4750 		assert(map.contains(i));
4751 		assert(*map.contains(i) == keyToValue(i));
4752 		assert(i in map);
4753 		assert(*(i in map) == keyToValue(i));
4754 		assert(map.length == i + 1);
4755 	}
4756 
4757 	void testRange() @trusted
4758 	{
4759 		size_t i = 0;
4760 		foreach (immutable keyValue; map[])
4761 		{
4762 			assert(keyValue.key == i);
4763 			assert(keyValue.value == keyToValue(cast(Key)i)); /+ TODO: use typed key instead of cast(Key) +/
4764 			++i;
4765 		}
4766 	}
4767 
4768 	testRange();
4769 }
4770 
4771 /** Check string types in `Keys`. */
4772 auto testString(Keys...)(size_t count, uint lengthMax)
4773 if (Keys.length != 0)
4774 {
4775 	import std.traits : isSomeString;
4776 	void testContainsAndInsert(Set, Key)(ref Set set, Key key)
4777 	if (isSomeString!Key)
4778 	{
4779 		import std.conv : to;
4780 		immutable failMessage = `Failed for key: "` ~ key.to!string ~ `"`;
4781 
4782 		assert(!set.contains(key), failMessage);
4783 
4784 		assert(set.insert(key), failMessage);
4785 		assert(set.contains(key), failMessage);
4786 
4787 		assert(!set.insert(key), failMessage);
4788 		assert(set.contains(key), failMessage);
4789 	}
4790 
4791 	import nxt.algorithm.comparison : equal;
4792 	import std.datetime.stopwatch : StopWatch, AutoStart;
4793 
4794 	foreach (Key; Keys)
4795 	{
4796 		auto set = RadixTreeSet!(Key, TestAllocator)();
4797 		assert(set.empty);
4798 
4799 		immutable sortedKeys = randomUniqueSortedStrings(count, lengthMax);
4800 
4801 		auto sw1 = StopWatch(AutoStart.yes);
4802 		foreach (immutable key; sortedKeys)
4803 		{
4804 			testContainsAndInsert(set, key);
4805 		}
4806 		sw1.stop;
4807 
4808 		version (show)
4809 		{
4810 			import std.conv : to;
4811 			version (show) import std.stdio : writeln;
4812 			version (show) writeln("Test for contains and insert took ", sw1.peek());
4813 		}
4814 
4815 		auto sw2 = StopWatch(AutoStart.yes);
4816 
4817 		void testPrefix() @trusted
4818 		{
4819 			assert(set[].equal(sortedKeys));
4820 			import std.algorithm.iteration : filter, map;
4821 			assert(set.prefix("a")
4822 					  .equal(sortedKeys.filter!(x => x.length && x[0] == 'a')
4823 									   .map!(x => x[1 .. $])));
4824 			assert(set.prefix("aa")
4825 					  .equal(sortedKeys.filter!(x => x.length >= 2 && x[0] == 'a' && x[1] == 'a')
4826 									   .map!(x => x[2 .. $])));
4827 		}
4828 
4829 		testPrefix();
4830 
4831 		sw2.stop;
4832 		version (show)
4833 		{
4834 			import std.stdio : writeln;
4835 			writeln("Compare took ", sw2.peek());
4836 		}
4837 	}
4838 }
4839 
4840 ///
4841 @safe /* TODO: pure nothrow @nogc */
4842 unittest
4843 { version (showAssertTags) dbg();
4844 	testString!(string)(512, 8);
4845 	testString!(string)(512, 32);
4846 }
4847 
4848 /// test map to values of type `bool`
4849 pure nothrow @safe @nogc unittest
4850 { version (showAssertTags) dbg();
4851 	alias Key = uint;
4852 	alias Value = bool;
4853 
4854 	auto map = RadixTreeMap!(Key, Value, TestAllocator)();
4855 	assert(map.empty);
4856 
4857 	static assert(map.hasValue);
4858 	map.insert(Key.init, Value.init);
4859 }
4860 
4861 /// test packing of set elements
4862 pure nothrow @safe @nogc unittest
4863 { version (showAssertTags) dbg();
4864 	auto set = RadixTreeSet!(ulong, TestAllocator)();
4865 	enum N = HeptLeaf1.capacity;
4866 
4867 	foreach (immutable i; 0 .. N)
4868 	{
4869 		assert(!set.contains(i));
4870 
4871 		assert(set.insert(i));
4872 		assert(set.contains(i));
4873 
4874 		assert(!set.insert(i));
4875 		assert(set.contains(i));
4876 	}
4877 
4878 	foreach (immutable i; N .. 256)
4879 	{
4880 		assert(!set.contains(i));
4881 
4882 		assert(set.insert(i));
4883 		assert(set.contains(i));
4884 
4885 		assert(!set.insert(i));
4886 		assert(set.contains(i));
4887 	}
4888 
4889 	foreach (immutable i; 256 .. 256 + N)
4890 	{
4891 		assert(!set.contains(i));
4892 
4893 		assert(set.insert(i));
4894 		assert(set.contains(i));
4895 
4896 		assert(!set.insert(i));
4897 		assert(set.contains(i));
4898 	}
4899 
4900 	foreach (immutable i; 256 + N .. 256 + 256)
4901 	{
4902 		assert(!set.contains(i));
4903 
4904 		assert(set.insert(i));
4905 		assert(set.contains(i));
4906 
4907 		assert(!set.insert(i));
4908 		assert(set.contains(i));
4909 	}
4910 }
4911 
4912 ///
4913 pure nothrow @safe @nogc unittest
4914 { version (showAssertTags) dbg();
4915 	auto set = RadixTreeSet!(ubyte, TestAllocator)();
4916 	alias Set = typeof(set);
4917 
4918 	foreach (immutable i; 0 .. HeptLeaf1.capacity)
4919 	{
4920 		assert(!set.contains(i));
4921 
4922 		assert(set.insert(i));
4923 		assert(set.contains(i));
4924 
4925 		assert(!set.insert(i));
4926 		assert(set.contains(i));
4927 
4928 		immutable rootRef = set.root.peek!(HeptLeaf1);
4929 		assert(rootRef);
4930 	}
4931 
4932 	foreach (immutable i; HeptLeaf1.capacity .. 256)
4933 	{
4934 		assert(!set.contains(i));
4935 
4936 		assert(set.insert(i));
4937 		assert(set.contains(i));
4938 
4939 		assert(!set.insert(i));
4940 		assert(set.contains(i));
4941 	}
4942 }
4943 
4944 ///
4945 pure nothrow @safe @nogc unittest
4946 { version (showAssertTags) dbg();
4947 	import std.meta : AliasSeq;
4948 	foreach (T; AliasSeq!(ushort, uint))
4949 	{
4950 		auto set = RadixTreeSet!(T, TestAllocator)();
4951 		alias Set = typeof(set);
4952 
4953 		foreach (immutable i; 0 .. 256)
4954 		{
4955 			assert(!set.contains(i));
4956 
4957 			assert(set.insert(i));
4958 			assert(set.contains(i));
4959 
4960 			assert(!set.insert(i));
4961 			assert(set.contains(i));
4962 		}
4963 
4964 		// 256
4965 		assert(!set.contains(256));
4966 
4967 		assert(set.insert(256));
4968 		assert(set.contains(256));
4969 
4970 		assert(!set.insert(256));
4971 		assert(set.contains(256));
4972 
4973 		// 257
4974 		assert(!set.contains(257));
4975 
4976 		assert(set.insert(257));
4977 		assert(set.contains(257));
4978 
4979 		assert(!set.insert(257));
4980 		assert(set.contains(257));
4981 
4982 		immutable rootRef = set.root.peek!(Set.DefaultBranchType*);
4983 		assert(rootRef);
4984 		immutable root = *rootRef;
4985 		assert(root.prefix.length == T.sizeof - 2);
4986 
4987 	}
4988 }
4989 
4990 /** Generate `count` number of random unique strings of minimum length 1 and
4991 	maximum length of `lengthMax`.
4992  */
4993 private static auto randomUniqueSortedStrings(size_t count, uint lengthMax) @trusted pure nothrow
4994 {
4995 	import std.random : Random, uniform;
4996 	auto gen = Random();
4997 
4998 	// store set of unique keys using a builtin D associative array (AA)
4999 	bool[string] stringSet;  // set of strings using D's AA
5000 
5001 	try
5002 	{
5003 		while (stringSet.length < count)
5004 		{
5005 			const length = uniform(1, lengthMax, gen);
5006 			auto key = new char[length];
5007 			foreach (immutable ix; 0 .. length)
5008 			{
5009 				key[ix] = cast(char)('a' + 0.uniform(26, gen));
5010 			}
5011 			stringSet[key[].idup] = true;
5012 		}
5013 	}
5014 	catch (Exception e)
5015 	{
5016 		import nxt.debugio : dbg;
5017 		dbg("Couldn't randomize");
5018 	}
5019 
5020 	import std.array : array;
5021 	import std.algorithm.sorting : sort;
5022 	auto keys = stringSet.byKey.array;
5023 	sort(keys);
5024 	return keys;
5025 }
5026 
5027 /** Create a set of words from the file `/usr/share/dict/words`. */
5028 private void benchmarkReadDictWords(Value)(in size_t maxCount)
5029 {
5030 	import std.range : chain;
5031 
5032 	const path = "test/data/dict-words";
5033 
5034 	enum hasValue = !is(Value == void);
5035 	static if (hasValue)
5036 		auto rtr = RadixTreeMap!(string, Value, TestAllocator)();
5037 	else
5038 		auto rtr = RadixTreeSet!(string, TestAllocator)();
5039 	assert(rtr.empty);
5040 
5041 	enum show = false;
5042 
5043 	string[] firsts = [];	   // used to test various things
5044 	size_t count = 0;
5045 
5046 	import std.datetime.stopwatch : StopWatch, AutoStart;
5047 	auto sw = StopWatch(AutoStart.yes);
5048 
5049 	import std.stdio : File;
5050 	foreach (const word; chain(firsts, File(path).byLine))
5051 	{
5052 		if (count >= maxCount) { break; }
5053 		import nxt.algorithm.searching : endsWith;
5054 		import std.range.primitives : empty;
5055 		if (!word.empty &&
5056 			!word.endsWith(`'s`)) // skip genitive forms
5057 		{
5058 			assert(!rtr.contains(word));
5059 
5060 			static if (show)
5061 			{
5062 				import std.string : representation;
5063 				// dbg(`word:"`, word, `" of length:`, word.length,
5064 				//	 ` of representation:`, word.representation);
5065 				// debug rtr.willFail = word == `amiable`;
5066 				// if (rtr.willFail)
5067 				// {
5068 				//	 rtr.print();
5069 				// }
5070 			}
5071 
5072 			static if (hasValue)
5073 			{
5074 				assert(rtr.insert(word, count));
5075 				const hitA = rtr.contains(word);
5076 				assert(hitA);
5077 				assert(*hitA == count);
5078 
5079 				assert(!rtr.insert(word, count));
5080 				const hitB = rtr.contains(word);
5081 				assert(hitB);
5082 				assert(*hitB == count);
5083 			}
5084 			else
5085 			{
5086 				assert(rtr.insert(word));
5087 				assert(rtr.contains(word));
5088 
5089 				assert(!rtr.insert(word));
5090 				assert(rtr.contains(word));
5091 			}
5092 			++count;
5093 		}
5094 	}
5095 	sw.stop;
5096 	version (show)
5097 	{
5098 		import std.stdio : writeln;
5099 		writeln("Added ", count, " words from ", path, " in ", sw.peek());
5100 		rtr.showStatistics();
5101 	}
5102 }
5103 
5104 unittest
5105 { version (showAssertTags) dbg();
5106 	const maxCount = 1000;
5107 	benchmarkReadDictWords!(void)(maxCount);
5108 	benchmarkReadDictWords!(size_t)(maxCount);
5109 }
5110 
5111 static private void testSlice(T)(ref T x) @trusted
5112 {
5113 	auto xr = x[];
5114 }
5115 
5116 bool testEqual(T, U)(ref T x, ref U y) @trusted
5117 {
5118 	import nxt.algorithm.comparison : equal;
5119 	return equal(x[], y[]);
5120 }
5121 
5122 /** Check correctness when span is `span` and for each `Key` in `Keys`. */
5123 version (unittest)
5124 private auto checkNumeric(Keys...)() @safe
5125 if (Keys.length != 0)
5126 {
5127 	import std.algorithm.comparison : min, max;
5128 	import std.traits : isIntegral, isFloatingPoint;
5129 	foreach (immutable it; 0 .. 1)
5130 	{
5131 		struct TestValueType { int i = 42; float f = 43; char ch = 'a'; }
5132 		alias Value = TestValueType;
5133 		import std.meta : AliasSeq;
5134 		foreach (Key; Keys)
5135 		{
5136 			auto set = RadixTreeSet!(Key, TestAllocator)();
5137 			auto map = RadixTreeMap!(Key, Value, TestAllocator)();
5138 
5139 			assert(set.empty);
5140 			assert(map.empty);
5141 
5142 			testSlice(set);
5143 			testSlice(map);
5144 
5145 			static assert(!set.hasValue);
5146 			static assert(map.hasValue);
5147 
5148 			static if (is(Key == bool) ||
5149 					   isIntegral!Key ||
5150 					   isFloatingPoint!Key)
5151 			{
5152 				static if (isIntegral!Key)
5153 				{
5154 					immutable low = max(Key.min, -1000);
5155 					immutable high = min(Key.max, 1000);
5156 					immutable factor = 1;
5157 				}
5158 				else static if (isFloatingPoint!Key)
5159 				{
5160 					immutable low = -1_000;
5161 					immutable high = 1_000;
5162 					immutable factor = 100;
5163 				}
5164 				else static if (is(Key == bool))
5165 				{
5166 					immutable low = false;
5167 					immutable high = true;
5168 					immutable factor = 1;
5169 				}
5170 				else
5171 				{
5172 					static assert(false, "Support Key " ~ Key.stringof);
5173 				}
5174 				immutable length = high - low + 1;
5175 
5176 				foreach (immutable uk_; low .. high + 1)
5177 				{
5178 					immutable uk = factor*uk_;
5179 					immutable Key key = cast(Key)uk;
5180 
5181 					assert(!set.contains(key)); // `key` should not yet be in `set`
5182 					assert(!map.contains(key)); // `key` should not yet be in 'map
5183 
5184 					assert(key !in set);		// alternative syntax
5185 					assert(key !in map);		// alternative syntax
5186 
5187 					// insertion of new `key` is `true` (previously not stored)
5188 					assert(set.insert(key));
5189 					assert(map.insert(key, Value.init));
5190 
5191 					// `key` should now be in `set` and `map`
5192 					assert(set.contains(key));
5193 					assert(map.contains(key));
5194 
5195 					// reinsertion of same `key` is `false` (already in stored)
5196 					assert(!set.insert(key));
5197 					assert(!map.insert(key, Value.init));
5198 
5199 					// `key` should now be `stored`
5200 					assert(set.contains(key));
5201 					assert(map.contains(key));
5202 
5203 					// alternative syntax
5204 					assert(key in set);
5205 					assert(key in map);
5206 
5207 					if (key != Key.max)		// except last value
5208 						assert(!set.contains(cast(Key)(key + 1))); // next key is not yet in set
5209 				}
5210 				assert(set.length == length);
5211 			}
5212 			else
5213 			{
5214 				static assert(false, `Handle type: "` ~ Key.stringof ~ `"`);
5215 			}
5216 
5217 			auto setDup = set.dup();
5218 			auto mapDup = map.dup();
5219 
5220 			assert(testEqual(set, setDup));
5221 			assert(testEqual(map, mapDup));
5222 
5223 			set.clear();
5224 
5225 			static assert(map.hasValue);
5226 			map.clear();
5227 		}
5228 	}
5229 }
5230 
5231 /** Benchmark performance and memory usage when span is `span`. */
5232 version (nxt_benchmark)
5233 private void benchmarkTimeAndSpace()
5234 {
5235 	version (show) import std.stdio : writeln;
5236 
5237 	struct TestValueType { int i = 42; float f = 43; char ch = 'a'; }
5238 	alias Value = TestValueType;
5239 	import std.meta : AliasSeq;
5240 	foreach (Key; AliasSeq!(uint)) // just benchmark uint for now
5241 	{
5242 		auto set = RadixTreeSet!(Key);
5243 		alias Set = set;
5244 		assert(set.empty);
5245 
5246 		static assert(!set.hasValue);
5247 
5248 		version (show) import std.datetime.stopwatch : StopWatch, AutoStart;
5249 
5250 		enum n = 1_000_000;
5251 
5252 		immutable useUniqueRandom = false;
5253 
5254 		import std.range : generate, take;
5255 		import std.random : uniform;
5256 		auto randomSamples = generate!(() => uniform!Key).take(n);
5257 
5258 		{
5259 			version (show) auto sw = StopWatch(AutoStart.yes);
5260 
5261 			foreach (immutable Key k; randomSamples)
5262 			{
5263 				if (useUniqueRandom)
5264 					assert(set.insert(k));
5265 				else
5266 					set.insert(k);
5267 
5268 				/* second insert of same key should always return `false` to
5269 				   indicate that key was already stored */
5270 				static if (false) { assert(!set.insert(k)); }
5271 			}
5272 
5273 			version (show) writeln("trie: Added ", n, " ", Key.stringof, "s of size ", n*Key.sizeof/1e6, " megabytes in ", sw.peek());
5274 			set.showStatistics();
5275 		}
5276 
5277 		{
5278 			version (show) auto sw = StopWatch(AutoStart.yes);
5279 			bool[int] aa;
5280 
5281 			foreach (Key k; randomSamples) { aa[k] = true; }
5282 
5283 			version (show) writeln("D-AA: Added ", n, " ", Key.stringof, "s of size ", n*Key.sizeof/1e6, " megabytes in ", sw.peek());
5284 		}
5285 
5286 		auto map = RadixTreeMap!(Key, Value);
5287 		assert(map.empty);
5288 		static assert(map.hasValue);
5289 
5290 		map.insert(Key.init, Value.init);
5291 	}
5292 }
5293 
5294 ///
5295 pure nothrow @safe @nogc unittest
5296 { version (showAssertTags) dbg();
5297 	struct S { int x; }
5298 
5299 	alias Key = S;
5300 	auto set = RadixTreeSet!(Key, TestAllocator)();
5301 
5302 	assert(!set.contains(S(43)));
5303 
5304 	assert(set.insert(S(43)));
5305 	assert(set.contains(S(43)));
5306 
5307 	assert(!set.insert(S(43)));
5308 	assert(set.contains(S(43)));
5309 
5310 	set.clear();
5311 }
5312 
5313 /** Static Iota.
5314  *
5315  * TODO: Move to Phobos std.range.
5316  */
5317 template iota(size_t from, size_t to)
5318 if (from <= to)
5319 {
5320 	alias iota = iotaImpl!(to - 1, from);
5321 }
5322 private template iotaImpl(size_t to, size_t now)
5323 {
5324 	import std.meta : AliasSeq;
5325 	static if (now >= to)
5326 		alias iotaImpl = AliasSeq!(now);
5327 	else
5328 		alias iotaImpl = AliasSeq!(now, iotaImpl!(to, now + 1));
5329 }
5330 
5331 unittest {
5332 	version (showAssertTags) dbg();
5333 	version (nxt_benchmark) benchmarkTimeAndSpace();
5334 }
5335 
5336 version (unittest)
5337 {
5338 	version (showAssertTags) import nxt.debugio : dbg;
5339 	import std.experimental.allocator.mallocator : TestAllocator = Mallocator;
5340 	// import std.experimental.allocator.gc_allocator : TestAllocator = GCAllocator;
5341 }