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 }