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 }