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 }