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