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