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