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