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