1 module nxt.variant;
2 
3 @safe pure:
4 
5 /** Lightweight version of $(D std.variant.Algebraic) that doesn't rely on `TypeInfo`.
6  *
7  * Member functions are, when possible, `pure nothrow @safe @nogc`.
8  *
9  * Storage (packing) is more space-efficient.
10  *
11  * TODO: support implicit conversions of (un)signed integer type to larger type
12  * See_Also: https://forum.dlang.org/post/jfasmgwoffmbtuvrtxey@forum.dlang.org
13  *
14  * TODO: add warnings about combining byte, short, int, long, etc.
15  * TODO: add warnings about combining ubyte, ushort, uint, ulong, etc.
16  *
17  * TODO: Use
18  *
19  * align(1)
20  * struct Unaligned
21  * {
22  * align(1):
23  * ubyte filler;
24  * Victim* p;
25  * }
26  *
27  * See_Also: https://github.com/Geod24/minivariant
28  * See_Also: http://forum.dlang.org/post/osfrjcuabwscvrecuvre@forum.dlang.org
29  * See_Also: https://forum.dlang.org/post/jfasmgwoffmbtuvrtxey@forum.dlang.org
30  * See_Also: https://forum.dlang.org/post/tdviqyzrcpttwwnvlzpv@forum.dlang.org
31  * See_Also: https://issues.dlang.org/show_bug.cgi?id=15399
32  */
33 struct Algebraic(Types...) {
34 @safe:
35 
36 	alias Ix = ubyte; // type index type. TODO: use uint or size_t when there is room (depending on `memoryPacked`)
37 	enum maxTypesCount = 2^^(Ix.sizeof * 8) - 1; // maximum number of allowed type parameters
38 
39 	import core.internal.traits : Unqual; /+ TODO: remove by using Andreis trick with `immutable` qualifier +/
40 	import std.meta : anySatisfy, allSatisfy, staticIndexOf;
41 	import std.traits : StdCommonType = CommonType, hasIndirections, hasAliasing;
42 	import nxt.traits_ex : isComparable, isEquable, sizesOf, stringsOf, allSame;
43 
44 public:
45 
46 	enum name = Algebraic.stringof;
47 	alias CommonType = StdCommonType!Types;
48 	enum hasCommonType = !is(CommonType == void);
49 
50 	enum typeCount = Types.length;
51 	enum typeSizes = sizesOf!Types;
52 	enum typeNames = stringsOf!Types;
53 
54 	/// Is `true` iff `this` may have aliasing through any of `Types`.
55 	enum mayHaveAliasing = anySatisfy!(hasAliasing, Types);
56 
57 	immutable static typeNamesRT = [typeNames]; // typeNames accessible at run-time, because `[typeNames]` is not @nogc
58 
59 	/// Is $(D true) if all $(D Types) stored in this $(D Algebraic) has the same length.
60 	enum hasFixedSize = allSame!typeSizes;
61 
62 	private enum N = typeCount; // useful local shorthand
63 
64 	private enum indexOf(T) = staticIndexOf!(T, Types); /+ TODO: cast to ubyte if N is <= 256 +/
65 
66 	// static checking
67 	static assert(N >= 1, "No use storing zero types in a " ~ name);
68 	static assert(N < maxTypesCount,
69 				  "Cannot store more than " ~ maxTypesCount.stringof ~ " Types in a " ~ name);
70 
71 	/** Is `true` if `U` is allowed to be assigned to `this`. */
72 	enum bool allowsAssignmentFrom(U) = ((N == 0 ||
73 										  indexOf!(U) >= 0 ||	  // either direct match or
74 										  ((!hasIndirections!U) && // no indirections and
75 										   indexOf!(Unqual!U) >= 0))); // ok to remove constness of value types
76 
77 	import nxt.maxsize_trait : maxSizeOf;
78 
79 	enum dataMaxSize = maxSizeOf!Types;
80 
81 	auto ref to(U)() const {
82 		import std.conv : to;
83 		final switch (typeIndex) {
84 			foreach (const i, T; Types) {
85 			case i: return as!T.to!U;
86 			}
87 		}
88 	}
89 
90 	void toString(Sink)(ref scope Sink sink) const /*tlm*/ {
91 		import std.conv : to;
92 		if (!hasValue)
93 			return sink("<Uninitialized Algebraic>");
94 		final switch (typeIndex) {
95 			foreach (const i, T; Types) {
96 			case i:
97 				/+ TODO: use instead to avoid allocations +/
98 				// import std.format : formatValue;
99 				// formatValue(sink, as!T);
100 				sink(to!string(as!T));
101 				return;
102 			}
103 		}
104 	}
105 
106 	/** Returns: $(D this) as a HTML-tagged $(D string). */
107 	@property void toHTML(Sink)(ref scope Sink sink) const /*tlm*/ {
108 		// wrap information in HTML tags with CSS propertie
109 		immutable tag = `dlang-` ~ typeName;
110 		sink(`<`); sink(tag); sink(`>`);
111 		toString(sink);
112 		sink(`</`); sink(tag); sink(`>`);
113 	}
114 
115 	pure:
116 
117 	/** Returns: Name (as a $(D string)) of Currently Stored Type. */
118 	private auto ref typeName()() const @safe nothrow @nogc /*tlm*/	{
119 		pragma(inline, true);
120 		return hasValue ? typeNamesRT[typeIndex] : null;
121 	}
122 
123 	/** Copy construct from `that`. */
124 	this()(in Algebraic that) @safe nothrow @nogc {
125 		_store = that._store;
126 		_tix = that._tix;
127 		pragma(msg, "Run postblits for " ~ Types.stringof);
128 	}
129 
130 	/// Destruct.
131 	~this() nothrow @nogc {
132 		pragma(inline, true);
133 		if (hasValue)
134 			release();
135 	}
136 
137 	/// Construct copy from `that`.
138 	this(T)(T that) @trusted nothrow @nogc if (allowsAssignmentFrom!T) {
139 		import core.lifetime : moveEmplace;
140 
141 		alias MT = Unqual!T;
142 		static if (__traits(isPOD, MT))
143 			*cast(MT*)(&_store) = that;
144 		else
145 			moveEmplace(that, *cast(MT*)(&_store)); /+ TODO: ok when `that` has indirections? +/
146 
147 		_tix = cast(Ix)(indexOf!MT + 1); // set type tag
148 	}
149 
150 	Algebraic opAssign(T)(T that) @trusted nothrow @nogc if (allowsAssignmentFrom!T) {
151 		import core.lifetime : moveEmplace;
152 
153 		if (hasValue)
154 			release();
155 
156 		alias MT = Unqual!T;
157 		static if (__traits(isPOD, MT))
158 			*cast(MT*)(&_store) = that;
159 		else
160 			moveEmplace(that, *cast(MT*)(&_store)); /+ TODO: ok when `that` has indirections? +/
161 
162 		_tix = cast(Ix)(indexOf!MT + 1); // set type tag
163 
164 		return this;
165 	}
166 
167 	/** If the $(D Algebraic) object holds a value of the $(I exact) type $(D T),
168 		returns a pointer to that value. Otherwise, returns $(D null). In cases
169 		where $(D T) is statically disallowed, $(D peek) will not compile.
170 	*/
171 	@property inout(T)* peek(T)() inout @trusted nothrow @nogc {
172 		pragma(inline, true);
173 		alias MT = Unqual!T;
174 		static if (!is(MT == void))
175 			static assert(allowsAssignmentFrom!MT, "Cannot store a " ~ MT.stringof ~ " in a " ~ name);
176 		if (!ofType!MT)
177 			return null;
178 		return cast(inout MT*)&_store; /+ TODO: alignment +/
179 	}
180 
181 	/// Get Value of type $(D T).
182 	@property auto ref inout(T) get(T)() inout @trusted {
183 		version (LDC) pragma(inline, true); // DMD cannot inline
184 		if (!ofType!T)
185 			throw new AlgebraicException("Algebraic doesn't contain type");
186 		return as!T;
187 	}
188 
189 	/// ditto
190 	@property inout(Types[index]) get(uint index)() inout @safe if (index < Types.length) {
191 		pragma(inline, true);
192 		return get!(Types[index]);
193 	}
194 
195 	/** Interpret data as type $(D T).
196 	 *
197 	 * See_Also: https://forum.dlang.org/post/thhrulbqsxbtzoyojqwx@forum.dlang.org
198 	 */
199 	private @property auto ref inout(T) as(T)() inout @system nothrow @nogc {
200 		static if (_store.alignof >= T.alignof)
201 			return *(cast(T*)&_store);
202 		else {
203 			inout(T) result;
204 			(cast(ubyte*)&result)[0 .. T.sizeof] = _store[0 .. T.sizeof];
205 			return result;
206 		}
207 	}
208 
209 	/// Returns: $(D true) iff $(D this) $(D Algebraic) can store an instance of $(D T).
210 	bool ofType(T)() const @safe nothrow @nogc { /+ TODO: shorter name such `isA`, `ofType` +/
211 		pragma(inline, true);
212 		return _tix == indexOf!T + 1;
213 	}
214 	alias canStore = ofType;
215 	alias isType = ofType; // minivariant compliance
216 
217 	/// Force $(D this) to the null/uninitialized/unset/undefined state.
218 	void clear() @safe nothrow @nogc {
219 		pragma(inline, true);
220 		if (_tix != _tix.init) {
221 			release();
222 			_tix = _tix.init; // this is enough to indicate undefined, no need to zero `_store`
223 		}
224 	}
225 	/// ditto
226 	alias nullify = clear;	  // compatible with `std.typecons.Nullable`
227 
228 	/// Nullable type support.
229 	static immutable nullValue = typeof(this).init;
230 
231 	/// ditto
232 	void opAssign(typeof(null)) {
233 		pragma(inline, true);
234 		clear();
235 	}
236 
237 	/// Release internal store.
238 	private void release() @trusted nothrow @nogc {
239 		import core.internal.traits : hasElaborateDestructor;
240 		final switch (typeIndex) {
241 			foreach (const i, T; Types) {
242 			case i:
243 				static if (hasElaborateDestructor!T)
244 					.destroy(*cast(T*)&_store); // reinterpret
245 				return;
246 			}
247 			case Ix.max:
248 				return;
249 		}
250 		/+ TODO: don't call if all types satisfy traits_ex.isValueType +/
251 		// _store[] = 0; // slightly faster than: memset(&_store, 0, _store.sizeof);
252 	}
253 
254 	/// Returns: $(D true) if this has a defined value (is defined).
255 	bool hasValue() const @safe nothrow @nogc {
256 		pragma(inline, true);
257 		return _tix != _tix.init;
258 	}
259 
260 	bool isNull() const @safe nothrow @nogc	{
261 		pragma(inline, true);
262 		return _tix == _tix.init;
263 	}
264 
265 	size_t currentSize()() const @safe nothrow @nogc /*tlm*/ {
266 		if (isNull)
267 			return 0;
268 		final switch (typeIndex) {
269 			foreach (const i, const typeSize; typeSizes) {
270 			case i:
271 				return typeSize;
272 			}
273 		}
274 	}
275 
276 	/// Blindly Implicitly Convert Stored Value in $(D U).
277 	private U convertTo(U)() const @trusted nothrow {
278 		assert(hasValue);
279 		final switch (typeIndex) {
280 			foreach (const i, T; Types) {
281 			case i:
282 				return as!T;
283 			}
284 		}
285 	}
286 
287 	static if (hasCommonType) {
288 		CommonType commonValue() const @trusted pure nothrow @nogc {
289 			assert(hasValue);
290 			final switch (typeIndex) {
291 				foreach (const i, T; Types) {
292 				case i:
293 					return cast(CommonType)as!T;
294 				}
295 			}
296 		}
297 	}
298 
299 	static if (allSatisfy!(isEquable, Types)) {
300 		static if (hasCommonType) {
301 			bool opEquals()(in Algebraic that) const @trusted nothrow @nogc /* tlm, opEquals is nothrow @nogc */
302 			{
303 				if (_tix != that._tix)
304 					return (this.convertTo!CommonType ==
305 							that.convertTo!CommonType);
306 				if (!this.hasValue &&
307 					!that.hasValue)
308 					return true; /+ TODO: same behaviour as floating point NaN? +/
309 				final switch (typeIndex) {
310 					foreach (const i, T; Types) {
311 					case i:
312 						return this.as!T == that.as!T;
313 					}
314 				}
315 			}
316 		} else {
317 			bool opEquals()(in Algebraic that) const @trusted nothrow /*tlm*/
318 			{
319 				if (_tix != that._tix)
320 					return false; // this needs to be nothrow or otherwise x in aa will throw which is not desirable
321 
322 				if (!this.hasValue &&
323 					!that.hasValue)
324 					return true; /+ TODO: same behaviour as floating point NaN? +/
325 
326 				final switch (typeIndex) {
327 					foreach (const i, T; Types) {
328 					case i:
329 						return (this.as!T ==
330 								that.as!T);
331 					}
332 				}
333 
334 				assert(false); // this is for knet to compile but not in this module. TODO: remove when compiler is fixed
335 			}
336 		}
337 
338 		bool opEquals(T)(in T that) const @trusted nothrow {
339 			/+ TODO: assert failure only if none of the Types isComparable to T +/
340 			static assert (allowsAssignmentFrom!T,
341 						   "Cannot equal any possible type of " ~ Algebraic.stringof ~
342 						   " with " ~ T.stringof);
343 
344 			if (!ofType!T)
345 				return false; // throw new AlgebraicException("Cannot equal Algebraic with current type " ~ "[Types][typeIndex]" ~ " with different types " ~ "T.stringof");
346 			return (this.as!T == that);
347 		}
348 	}
349 
350 	static if (allSatisfy!(isComparable, Types)) {
351 		int opCmp()(in Algebraic that) const @trusted /* tlm, TODO: extend to Algebraic!(ThatTypes) */
352 		{
353 			static if (hasCommonType) { /+ TODO: extend to haveCommonType!(Types, ThatTypes) +/
354 				if (_tix != that._tix) {
355 					/+ TODO: functionize to defaultOpCmp to avoid postblits: +/
356 					const a = this.convertTo!CommonType;
357 					const b = that.convertTo!CommonType;
358 					return a < b ? -1 : a > b ? 1 : 0;
359 				}
360 			} else {
361 				if (_tix != that._tix)
362 					throw new AlgebraicException("Cannot compare Algebraic of type " ~ typeNamesRT[typeIndex] ~
363 													  " with Algebraic of type " ~ typeNamesRT[that.typeIndex]);
364 			}
365 
366 			final switch (typeIndex) {
367 				foreach (const i, T; Types) {
368 				case i:
369 					/+ TODO: functionize to defaultOpCmp to avoid postblits: +/
370 					const a = this.as!T;
371 					const b = that.as!T;
372 					return a < b ? -1 : a > b ? 1 : 0;
373 				}
374 			}
375 		}
376 
377 		int opCmp(U)(in U that) const @trusted {
378 			/+ TODO: is CommonType or isComparable the correct way of checking this? +/
379 			static if (!is(StdCommonType!(Types, U) == void)) {
380 				final switch (typeIndex) {
381 					foreach (const i, T; Types) {
382 					case i:
383 						const a = this.as!T;
384 						return a < that ? -1 : a > that ? 1 : 0; /+ TODO: functionize to defaultOpCmp +/
385 					}
386 				}
387 			} else {
388 				static assert(allowsAssignmentFrom!U, /+ TODO: relax to allowsComparisonWith!U +/
389 							  "Cannot compare " ~ Algebraic.stringof ~ " with " ~ U.stringof);
390 				if (!ofType!U)
391 					throw new AlgebraicException("Cannot compare " ~ Algebraic.stringof ~ " with " ~ U.stringof);
392 				/+ TODO: functionize to defaultOpCmp to avoid postblits: +/
393 				const a = this.as!U;
394 				return a < that ? -1 : a > that ? 1 : 0;
395 			}
396 		}
397 	}
398 
399 	extern (D) hash_t toHash() const @trusted pure nothrow {
400 		import core.internal.hash : hashOf;
401 		const typeof(return) hash = _tix.hashOf;
402 		if (hasValue) {
403 			final switch (typeIndex) {
404 				foreach (const i, T; Types) {
405 				case i: return as!T.hashOf(hash);
406 				}
407 			}
408 		}
409 		return hash;
410 	}
411 
412 	import std.digest : isDigest;
413 
414 	//+ TODO: use `!hasAliasing`? +/
415 	void toDigest(Digest)(scope ref Digest digest) const nothrow @nogc if (isDigest!Digest) {
416 		import nxt.digestion : digestAny;
417 		digestAny(digest, _tix);
418 		if (hasValue) {
419 			final switch (typeIndex) {
420 				foreach (const i, T; Types) {
421 				case i:
422 					digestAny(digest, as!T);
423 					return;
424 				}
425 			}
426 		}
427 	}
428 
429 private:
430 	// immutable to make hasAliasing!(Algebraic!(...)) false
431 	union {
432 		//align(8):
433 		static if (mayHaveAliasing) {
434 			ubyte[dataMaxSize] _store;
435 			void* alignDummy; // non-packed means good alignment. TODO: check for maximum alignof of Types
436 		} else {
437 			// to please hasAliasing!(typeof(this)):
438 			immutable(ubyte)[dataMaxSize] _store;
439 			immutable(void)* alignDummy; // non-packed means good alignment. TODO: check for maximum alignof of Types
440 		}
441 	}
442 
443 	size_t typeIndex() const nothrow @nogc {
444 		pragma(inline, true);
445 		assert(_tix != 0, "Cannot get index from uninitialized (null) variant.");
446 		return _tix - 1;
447 	}
448 
449 	Ix _tix = 0;				// type index
450 }
451 
452 /// Algebraic type exception.
453 static class AlgebraicException : Exception {
454 	this(string s) pure @nogc {
455 		super(s);
456 	}
457 }
458 
459 unittest {
460 	// Algebraic!(float, double, bool) a;
461 	// a = 2.1;  assert(a.to!string == "2.1");  assert(a.toHTML == "<dlang-double>2.1</dlang-double>");
462 	// a = 2.1f; assert(a.to!string == "2.1");  assert(a.toHTML == "<dlang-float>2.1</dlang-float>");
463 	// a = true; assert(a.to!string == "true"); assert(a.toHTML == "<dlang-bool>true</dlang-bool>");
464 }
465 
466 pure:
467 
468 /// equality and comparison
469 @trusted nothrow @nogc unittest {
470 	Algebraic!(float) a, b;
471 	static assert(a.hasFixedSize);
472 
473 	a = 1.0f;
474 	assert(a._tix != a.Ix.init);
475 
476 	b = 1.0f;
477 	assert(b._tix != b.Ix.init);
478 
479 	assert(a._tix == b._tix);
480 	assert((cast(ubyte*)&a)[0 .. a.sizeof] == (cast(ubyte*)&b)[0 .. b.sizeof]);
481 	assert(a == b);			 /+ TODO: this errors with dmd master +/
482 }
483 
484 ///
485 nothrow @nogc unittest {
486 	alias C = Algebraic!(float, double);
487 	C a = 1.0;
488 	const C b = 2.0;
489 	const C c = 2.0f;
490 	const C d = 1.0f;
491 
492 	assert(a.commonValue == 1);
493 	assert(b.commonValue == 2);
494 	assert(c.commonValue == 2);
495 	assert(d.commonValue == 1);
496 
497 	// nothrow comparison possible
498 	assert(a < b);
499 	assert(a < c);
500 	assert(a == d);
501 
502 	static assert(!a.hasFixedSize);
503 	static assert(a.allowsAssignmentFrom!float);
504 	static assert(a.allowsAssignmentFrom!double);
505 	static assert(!a.allowsAssignmentFrom!string);
506 
507 	a.clear();
508 	assert(!a.hasValue);
509 	assert(a.peek!float is null);
510 	assert(a.peek!double is null);
511 	assert(a.currentSize == 0);
512 }
513 
514 /// aliasing traits
515 nothrow @nogc unittest {
516 	import std.traits : hasAliasing;
517 	static assert(!hasAliasing!(Algebraic!(long, double)));
518 	static assert(!hasAliasing!(Algebraic!(long, string)));
519 	static assert(!hasAliasing!(Algebraic!(long, immutable(double)*)));
520 	static assert(hasAliasing!(Algebraic!(long, double*)));
521 }
522 
523 nothrow @nogc unittest {
524 	alias V = Algebraic!(long, double);
525 	const a = V(1.0);
526 
527 	static assert(a.hasFixedSize);
528 
529 	assert(a.ofType!double);
530 	assert(a.peek!long is null);
531 	assert(a.peek!double !is null);
532 
533 	static assert(is(typeof(a.peek!long) == const(long)*));
534 	static assert(is(typeof(a.peek!double) == const(double)*));
535 }
536 
537 /// equality and comparison
538 nothrow @nogc unittest {
539 	Algebraic!(int) a, b;
540 	static assert(a.hasFixedSize);
541 	a = 1;
542 	b = 1;
543 	assert(a == b);
544 }
545 
546 /// equality and comparison
547 nothrow @nogc unittest {
548 	Algebraic!(float) a, b;
549 	static assert(a.hasFixedSize);
550 	a = 1.0f;
551 	b = 1.0f;
552 	assert(a == b);
553 }
554 
555 /// equality and comparison
556 /*TODO: @nogc*/ unittest {
557 	Algebraic!(float, double, string) a, b;
558 
559 	static assert(!a.hasFixedSize);
560 
561 	a = 1.0f;
562 	b = 1.0f;
563 	assert(a == b);
564 
565 	a = 1.0f;
566 	b = 2.0f;
567 	assert(a != b);
568 	assert(a < b);
569 	assert(b > a);
570 
571 	a = "alpha";
572 	b = "alpha";
573 	assert(a == b);
574 
575 	a = "a";
576 	b = "b";
577 	assert(a != b);
578 	assert(a < b);
579 	assert(b > a);
580 }
581 
582 /// AA keys
583 nothrow unittest {
584 	alias C = Algebraic!(float, double);
585 	static assert(!C.hasFixedSize);
586 	string[C] a;
587 	a[C(1.0f)] = "1.0f";
588 	a[C(2.0)] = "2.0";
589 	assert(a[C(1.0f)] == "1.0f");
590 	assert(a[C(2.0)] == "2.0");
591 }
592 
593 /// verify nothrow comparisons
594 nothrow @nogc unittest {
595 	alias C = Algebraic!(int, float, double);
596 	static assert(!C.hasFixedSize);
597 	assert(C(1.0) < 2);
598 	assert(C(1.0) < 2.0);
599 	assert(C(1.0) < 2.0);
600 	static assert(!__traits(compiles, { C(1.0) < 'a'; })); // cannot compare with char
601 	static assert(!__traits(compiles, { C(1.0) < "a"; })); // cannot compare with string
602 }
603 
604 /// TODO
605 nothrow @nogc unittest {
606 	// alias C = Algebraic!(int, float, double);
607 	// alias D = Algebraic!(float, double);
608 	// assert(C(1) < D(2.0));
609 	// assert(C(1) < D(1.0));
610 	// static assert(!__traits(compiles, { C(1.0) < "a"; })); // cannot compare with string
611 }
612 
613 /// if types have CommonType comparison is nothrow @nogc
614 nothrow @nogc unittest {
615 	alias C = Algebraic!(short, int, long, float, double);
616 	static assert(!C.hasFixedSize);
617 	assert(C(1) != C(2.0));
618 	assert(C(1) == C(1.0));
619 }
620 
621 /// if types have `CommonType` then comparison is `nothrow @nogc`
622 nothrow @nogc unittest {
623 	alias C = Algebraic!(short, int, long, float, double);
624 	static assert(!C.hasFixedSize);
625 	assert(C(1) != C(2.0));
626 	assert(C(1) == C(1.0));
627 }
628 
629 nothrow @nogc unittest {
630 	alias C = Algebraic!(int, string);
631 	static assert(!C.hasFixedSize);
632 	C x;
633 	x = 42;
634 }
635 
636 nothrow @nogc unittest {
637 	alias C = Algebraic!(int);
638 	static assert(C.hasFixedSize);
639 	C x;
640 	x = 42;
641 }
642 
643 unittest {
644 	import core.internal.traits : hasElaborateCopyConstructor;
645 
646 	import std.exception : assertThrown;
647 
648 	static assert(hasElaborateCopyConstructor!(char[2]) == false);
649 	static assert(hasElaborateCopyConstructor!(char[]) == false);
650 
651 	// static assert(Algebraic!(char, wchar).sizeof == 2 + 1);
652 	// static assert(Algebraic!(wchar, dchar).sizeof == 4 + 1);
653 	// static assert(Algebraic!(long, double).sizeof == 8 + 1);
654 	// static assert(Algebraic!(int, float).sizeof == 4 + 1);
655 	// static assert(Algebraic!(char[2], wchar[2]).sizeof == 2 * 2 + 1);
656 
657 	alias C = Algebraic!(string,
658 						// fixed length strings: small string optimizations (SSOs)
659 						int, float,
660 						long, double);
661 	static assert(!C.hasFixedSize);
662 
663 	static assert(C.allowsAssignmentFrom!int);
664 	static assert(!C.allowsAssignmentFrom!(int[2]));
665 	static assert(C.allowsAssignmentFrom!(const(int)));
666 
667 	static assert(C.dataMaxSize == string.sizeof);
668 	static assert(!__traits(compiles, { assert(d == 'a'); }));
669 
670 	assert(C() == C());		 // two undefined are equal
671 
672 	C d;
673 	C e = d;					// copy construction
674 	assert(e == d);			 // two undefined should not equal
675 
676 	d = 11;
677 	assert(d != e);
678 
679 	/+ TODO: Allow this d = cast(ubyte)255; +/
680 
681 	d = 1.0f;
682 	assertThrown!AlgebraicException(d.get!double);
683 	assert(d.hasValue);
684 	assert(d.ofType!float);
685 	assert(d.peek!float !is null);
686 	assert(!d.ofType!double);
687 	assert(d.peek!double is null);
688 	assert(d.get!float == 1.0f);
689 	assert(d == 1.0f);
690 	assert(d != 2.0f);
691 	assert(d < 2.0f);
692 	assert(d != "2.0f");
693 	assertThrown!AlgebraicException(d < 2.0);
694 	assertThrown!AlgebraicException(d < "2.0");
695 	assert(d.currentSize == float.sizeof);
696 
697 	d = 2;
698 	assert(d.hasValue);
699 	assert(d.peek!int !is null);
700 	assert(!d.ofType!float);
701 	assert(d.peek!float is null);
702 	assert(d.get!int == 2);
703 	assert(d == 2);
704 	assert(d != 3);
705 	assert(d < 3);
706 	assertThrown!AlgebraicException(d < 2.0f);
707 	assertThrown!AlgebraicException(d < "2.0");
708 	assert(d.currentSize == int.sizeof);
709 
710 	d = "abc";
711 	assert(d.hasValue);
712 	assert(d.get!0 == "abc");
713 	assert(d.get!string == "abc");
714 	assert(d.ofType!string);
715 	assert(d.peek!string !is null);
716 	assert(d == "abc");
717 	assert(d != "abcd");
718 	assert(d < "abcd");
719 	assertThrown!AlgebraicException(d < 2.0f);
720 	assertThrown!AlgebraicException(d < 2.0);
721 	assert(d.currentSize == string.sizeof);
722 
723 	d = 2.0;
724 	assert(d.hasValue);
725 	assert(d.get!double == 2.0);
726 	assert(d.ofType!double);
727 	assert(d.peek!double !is null);
728 	assert(d == 2.0);
729 	assert(d != 3.0);
730 	assert(d < 3.0);
731 	assertThrown!AlgebraicException(d < 2.0f);
732 	assertThrown!AlgebraicException(d < "2.0");
733 	assert(d.currentSize == double.sizeof);
734 
735 	d.clear();
736 	assert(d.peek!int is null);
737 	assert(d.peek!float is null);
738 	assert(d.peek!double is null);
739 	assert(d.peek!string is null);
740 	assert(!d.hasValue);
741 	assert(d.currentSize == 0);
742 
743 	assert(C(1.0f) == C(1.0f));
744 	assert(C(1.0f) <  C(2.0f));
745 	assert(C(2.0f) >  C(1.0f));
746 
747 	assertThrown!AlgebraicException(C(1.0f) <  C(1.0));
748 	// assertThrown!AlgebraicException(C(1.0f) == C(1.0));
749 }
750 
751 ///
752 nothrow @nogc unittest {
753 	import nxt.container.static_array : MutableStringN;
754 	alias String15 = MutableStringN!(15);
755 
756 	String15 s;
757 	String15 t = s;
758 	assert(t == s);
759 
760 	alias V = Algebraic!(String15, string);
761 	V v = String15("first");
762 	assert(v.peek!String15);
763 	assert(!v.peek!string);
764 
765 	v = String15("second");
766 	assert(v.peek!String15);
767 	assert(!v.peek!string);
768 
769 	v = "third";
770 	assert(!v.peek!String15);
771 	assert(v.peek!string);
772 
773 	auto w = v;
774 	assert(v == w);
775 	w.clear();
776 	assert(!v.isNull);
777 	assert(w.isNull);
778 	w = v;
779 	assert(!w.isNull);
780 
781 	v = V.init;
782 	assert(v == V.init);
783 }
784 
785 /// check default values
786 nothrow @nogc unittest {
787 	import nxt.container.static_array : MutableStringN;
788 	alias String15 = MutableStringN!(15);
789 
790 	alias V = Algebraic!(String15, string);
791 	V _;
792 	assert(_._tix == V.Ix.init);
793 	assert(V.init._tix == V.Ix.init);
794 
795 	/+ TODO: import nxt.bit_traits : isInitAllZeroBits; +/
796 	/+ TODO: static assert(isInitAllZeroBits!(V)); +/
797 }