1 module nxt.variant;
2 
3 import nxt.dbgio : dbg;
4 
5 @safe pure:
6 
7 /** Lightweight version of $(D std.variant.Algebraic) that doesn't rely on `TypeInfo`.
8  *
9  * Member functions are, when possible, `@safe pure nothrow @nogc`.
10  *
11  * Storage (packing) is more space-efficient.
12  *
13  * TODO support implicit conversions of (un)signed integer type to larger type
14  * See_Also: https://forum.dlang.org/post/jfasmgwoffmbtuvrtxey@forum.dlang.org
15  *
16  * TODO add warnings about combining byte, short, int, long, etc.
17  * TODO add warnings about combining ubyte, ushort, uint, ulong, etc.
18  *
19  * TODO Use
20  *
21  * align(1)
22  * struct Unaligned
23  * {
24  * align(1):
25  * ubyte filler;
26  * Victim* p;
27  * }
28  *
29  * See_Also: http://forum.dlang.org/post/osfrjcuabwscvrecuvre@forum.dlang.org
30  * See_Also: https://forum.dlang.org/post/jfasmgwoffmbtuvrtxey@forum.dlang.org
31  * See_Also: https://forum.dlang.org/post/tdviqyzrcpttwwnvlzpv@forum.dlang.org
32  * See_Also: https://issues.dlang.org/show_bug.cgi?id=15399
33  */
34 struct Algebraic(Types...)
35 {
36 @safe:
37 
38     alias Ix = ubyte; // type index type. TODO use uint or size_t when there is room (depending on `memoryPacked`)
39     enum maxTypesCount = 2^^(Ix.sizeof * 8) - 1; // maximum number of allowed type parameters
40 
41     import core.internal.traits : Unqual; // TODO remove by using Andreis trick with `immutable` qualifier
42     import std.meta : anySatisfy, allSatisfy, staticIndexOf;
43     import std.traits : StdCommonType = CommonType, hasIndirections, hasAliasing;
44     import nxt.traits_ex : isComparable, isEquable, sizesOf, stringsOf, allSame;
45     import nxt.container_traits : needsMove;
46 
47 public:
48 
49     enum name = Algebraic.stringof;
50     alias CommonType = StdCommonType!Types;
51     enum hasCommonType = !is(CommonType == void);
52 
53     enum typeCount = Types.length;
54     enum typeSizes = sizesOf!Types;
55     enum typeNames = stringsOf!Types;
56 
57     /// Is `true` iff `this` may have aliasing through any of `Types`.
58     enum mayHaveAliasing = anySatisfy!(hasAliasing, Types);
59 
60     immutable static typeNamesRT = [typeNames]; // typeNames accessible at run-time, because `[typeNames]` is not @nogc
61 
62     /// Is $(D true) if all $(D Types) stored in this $(D Algebraic) has the same length.
63     enum hasFixedSize = allSame!typeSizes;
64 
65     private enum N = typeCount; // useful local shorthand
66 
67     private enum indexOf(T) = staticIndexOf!(T, Types); // TODO cast to ubyte if N is <= 256
68 
69     // static checking
70     static assert(N >= 1, "No use storing zero types in a " ~ name);
71     static assert(N < maxTypesCount,
72                   "Cannot store more than " ~ maxTypesCount.stringof ~ " Types in a " ~ name);
73 
74     /** Is `true` if `U` is allowed to be assigned to `this`. */
75     enum bool allowsAssignmentFrom(U) = ((N == 0 ||
76                                           indexOf!(U) >= 0 ||      // either direct match or
77                                           ((!hasIndirections!U) && // no indirections and
78                                            indexOf!(Unqual!U) >= 0))); // ok to remove constness of value types
79 
80     import nxt.maxsize_trait : maxSizeOf;
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     alias canStore = ofType;
248 
249     /// Force $(D this) to the null/uninitialized/unset/undefined state.
250     void clear() @safe nothrow @nogc
251     {
252         pragma(inline, true);
253         if (_tix != _tix.init)
254         {
255             release();
256             _tix = _tix.init; // this is enough to indicate undefined, no need to zero `_store`
257         }
258     }
259     /// ditto
260     alias nullify = clear;      // compatible with `std.typecons.Nullable`
261 
262     /// Nullable type support.
263     static immutable nullValue = typeof(this).init;
264 
265     /// ditto
266     void opAssign(typeof(null))
267     {
268         pragma(inline, true);
269         clear();
270     }
271 
272     /// Release internal store.
273     private void release() @trusted nothrow @nogc
274     {
275         import core.internal.traits : hasElaborateDestructor;
276         final switch (typeIndex)
277         {
278             foreach (const i, T; Types)
279             {
280             case i:
281                 static if (hasElaborateDestructor!T)
282                     .destroy(*cast(T*)&_store); // reinterpret
283                 return;
284             }
285             case Ix.max:
286                 return;
287         }
288         // TODO don't call if all types satisfy traits_ex.isValueType
289         // _store[] = 0; // slightly faster than: memset(&_store, 0, _store.sizeof);
290     }
291 
292     /// Returns: $(D true) if this has a defined value (is defined).
293     bool hasValue() const @safe nothrow @nogc
294     {
295         pragma(inline, true);
296         return _tix != _tix.init;
297     }
298 
299     bool isNull() const @safe nothrow @nogc
300     {
301         pragma(inline, true);
302         return _tix == _tix.init;
303     }
304 
305     size_t currentSize()() const @safe nothrow @nogc // template-lazy
306     {
307         if (isNull) { 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 }