1 module nxt.variant; 2 3 version(none): 4 5 import nxt.dbgio : dbg; 6 7 @safe pure: 8 9 /** Lightweight version of $(D std.variant.Algebraic) that doesn't rely on `TypeInfo`. 10 * 11 * Member functions are, when possible, `@safe pure nothrow @nogc`. 12 * 13 * Storage (packing) is more space-efficient. 14 * 15 * TODO: support implicit conversions of (un)signed integer type to larger type 16 * See_Also: https://forum.dlang.org/post/jfasmgwoffmbtuvrtxey@forum.dlang.org 17 * 18 * TODO: add warnings about combining byte, short, int, long, etc. 19 * TODO: add warnings about combining ubyte, ushort, uint, ulong, etc. 20 * 21 * TODO: Use 22 * 23 * align(1) 24 * struct Unaligned 25 * { 26 * align(1): 27 * ubyte filler; 28 * Victim* p; 29 * } 30 * 31 * See_Also: http://forum.dlang.org/post/osfrjcuabwscvrecuvre@forum.dlang.org 32 * See_Also: https://forum.dlang.org/post/jfasmgwoffmbtuvrtxey@forum.dlang.org 33 * See_Also: https://forum.dlang.org/post/tdviqyzrcpttwwnvlzpv@forum.dlang.org 34 * See_Also: https://issues.dlang.org/show_bug.cgi?id=15399 35 */ 36 struct Algebraic(Types...) 37 { 38 @safe: 39 40 alias Ix = ubyte; // type index type. TODO: use uint or size_t when there is room (depending on `memoryPacked`) 41 enum maxTypesCount = 2^^(Ix.sizeof * 8) - 1; // maximum number of allowed type parameters 42 43 import core.internal.traits : Unqual; // TODO: remove by using Andreis trick with `immutable` qualifier 44 import std.meta : anySatisfy, allSatisfy, staticIndexOf; 45 import std.traits : StdCommonType = CommonType, hasIndirections, hasAliasing; 46 import nxt.traits_ex : isComparable, isEquable, sizesOf, stringsOf, allSame; 47 import nxt.container_traits : needsMove; 48 49 public: 50 51 enum name = Algebraic.stringof; 52 alias CommonType = StdCommonType!Types; 53 enum hasCommonType = !is(CommonType == void); 54 55 enum typeCount = Types.length; 56 enum typeSizes = sizesOf!Types; 57 enum typeNames = stringsOf!Types; 58 59 /// Is `true` iff `this` may have aliasing through any of `Types`. 60 enum mayHaveAliasing = anySatisfy!(hasAliasing, Types); 61 62 immutable static typeNamesRT = [typeNames]; // typeNames accessible at run-time, because `[typeNames]` is not @nogc 63 64 /// Is $(D true) if all $(D Types) stored in this $(D Algebraic) has the same length. 65 enum hasFixedSize = allSame!typeSizes; 66 67 private enum N = typeCount; // useful local shorthand 68 69 private enum indexOf(T) = staticIndexOf!(T, Types); // TODO: cast to ubyte if N is <= 256 70 71 // static checking 72 static assert(N >= 1, "No use storing zero types in a " ~ name); 73 static assert(N < maxTypesCount, 74 "Cannot store more than " ~ maxTypesCount.stringof ~ " Types in a " ~ name); 75 76 /** Is `true` if `U` is allowed to be assigned to `this`. */ 77 enum bool allowsAssignmentFrom(U) = ((N == 0 || 78 indexOf!(U) >= 0 || // either direct match or 79 ((!hasIndirections!U) && // no indirections and 80 indexOf!(Unqual!U) >= 0))); // ok to remove constness of value types 81 82 import nxt.maxsize_trait : maxSizeOf; 83 84 enum dataMaxSize = maxSizeOf!Types; 85 86 auto ref to(U)() const // TODO: pure @nogc 87 { 88 import std.conv : to; 89 final switch (typeIndex) 90 { 91 foreach (const i, T; Types) 92 { 93 case i: return as!T.to!U; 94 } 95 } 96 } 97 98 @property void toString()(scope void delegate(scope const(char)[]) sink) const // template-lazy. TODO: pure 99 { 100 import std.conv : to; 101 if (!hasValue) 102 return sink("<Uninitialized Algebraic>"); 103 final switch (typeIndex) 104 { 105 foreach (const i, T; Types) 106 { 107 case i: 108 // TODO: use instead to avoid allocations 109 // import std.format : formatValue; 110 // formatValue(sink, as!T); 111 sink(to!string(as!T)); 112 return; 113 } 114 } 115 } 116 117 /** Returns: $(D this) as a HTML-tagged $(D string). */ 118 @property void toHTML()(scope void delegate(scope const(char)[]) sink) const // template-lazy. TODO: pure 119 { 120 // wrap information in HTML tags with CSS propertie 121 immutable tag = `dlang-` ~ typeName; 122 sink(`<`); sink(tag); sink(`>`); 123 toString(sink); 124 sink(`</`); sink(tag); sink(`>`); 125 } 126 127 pure: 128 129 /** Returns: Name (as a $(D string)) of Currently Stored Type. */ 130 private auto ref typeName()() const @safe nothrow @nogc // template-lazy 131 { 132 pragma(inline, true); 133 return hasValue ? typeNamesRT[typeIndex] : null; 134 } 135 136 /** Copy construct from `that`. */ 137 this()(in Algebraic that) @safe nothrow @nogc 138 { 139 _store = that._store; 140 _tix = that._tix; 141 pragma(msg, "Run postblits for " ~ Types.stringof); 142 } 143 144 /// Destruct. 145 ~this() @nogc 146 { 147 pragma(inline, true); 148 if (hasValue) 149 release(); 150 } 151 152 /// Construct copy from `that`. 153 this(T)(T that) @trusted nothrow @nogc 154 if (allowsAssignmentFrom!T) 155 { 156 import core.lifetime : moveEmplace; 157 158 alias MT = Unqual!T; 159 static if (needsMove!MT) 160 moveEmplace(that, 161 *cast(MT*)(&_store)); // TODO: ok when `that` has indirections? 162 else 163 *cast(MT*)(&_store) = that; 164 165 _tix = cast(Ix)(indexOf!MT + 1); // set type tag 166 } 167 168 Algebraic opAssign(T)(T that) @trusted nothrow @nogc 169 if (allowsAssignmentFrom!T) 170 { 171 import core.lifetime : moveEmplace; 172 173 if (hasValue) 174 release(); 175 176 alias MT = Unqual!T; 177 static if (needsMove!MT) 178 moveEmplace(that, *cast(MT*)(&_store)); // TODO: ok when `that` has indirections? 179 else 180 { 181 dbg(as!T, " = ", that); 182 *cast(MT*)(&_store) = that; 183 dbg(as!T, " = ", that); 184 } 185 dbg(as!T); 186 187 _tix = cast(Ix)(indexOf!MT + 1); // set type tag 188 189 return this; 190 } 191 192 /** If the $(D Algebraic) object holds a value of the $(I exact) type $(D T), 193 returns a pointer to that value. Otherwise, returns $(D null). In cases 194 where $(D T) is statically disallowed, $(D peek) will not compile. 195 */ 196 @property inout(T)* peek(T)() inout @trusted nothrow @nogc 197 { 198 pragma(inline, true); 199 alias MT = Unqual!T; 200 static if (!is(MT == void)) 201 static assert(allowsAssignmentFrom!MT, "Cannot store a " ~ MT.stringof ~ " in a " ~ name); 202 if (!ofType!MT) 203 return null; 204 return cast(inout MT*)&_store; // TODO: alignment 205 } 206 207 /// Get Value of type $(D T). 208 @property auto ref inout(T) get(T)() inout @trusted 209 { 210 version(LDC) pragma(inline, true); // DMD cannot inline 211 if (!ofType!T) 212 throw new AlgebraicException("Algebraic doesn't contain type"); 213 return as!T; 214 } 215 216 /// ditto 217 @property inout(Types[index]) get(uint index)() inout @safe 218 if (index < Types.length) 219 { 220 pragma(inline, true); 221 return get!(Types[index]); 222 } 223 224 /** Interpret data as type $(D T). 225 * 226 * See_Also: https://forum.dlang.org/post/thhrulbqsxbtzoyojqwx@forum.dlang.org 227 */ 228 private @property auto ref inout(T) as(T)() inout @system nothrow @nogc 229 { 230 static if (_store.alignof >= T.alignof) 231 return *(cast(T*)&_store); 232 else 233 { 234 inout(T) result; 235 (cast(ubyte*)&result)[0 .. T.sizeof] = _store[0 .. T.sizeof]; 236 return result; 237 } 238 } 239 240 /// Returns: $(D true) iff $(D this) $(D Algebraic) can store an instance of $(D T). 241 bool ofType(T)() const @safe nothrow @nogc // TODO: shorter name such `isA`, `ofType` 242 { 243 pragma(inline, true); 244 return _tix == indexOf!T + 1; 245 } 246 alias canStore = ofType; 247 248 /// Force $(D this) to the null/uninitialized/unset/undefined state. 249 void clear() @safe nothrow @nogc 250 { 251 pragma(inline, true); 252 if (_tix != _tix.init) 253 { 254 release(); 255 _tix = _tix.init; // this is enough to indicate undefined, no need to zero `_store` 256 } 257 } 258 /// ditto 259 alias nullify = clear; // compatible with `std.typecons.Nullable` 260 261 /// Nullable type support. 262 static immutable nullValue = typeof(this).init; 263 264 /// ditto 265 void opAssign(typeof(null)) 266 { 267 pragma(inline, true); 268 clear(); 269 } 270 271 /// Release internal store. 272 private void release() @trusted nothrow @nogc 273 { 274 import core.internal.traits : hasElaborateDestructor; 275 final switch (typeIndex) 276 { 277 foreach (const i, T; Types) 278 { 279 case i: 280 static if (hasElaborateDestructor!T) 281 .destroy(*cast(T*)&_store); // reinterpret 282 return; 283 } 284 case Ix.max: 285 return; 286 } 287 // TODO: don't call if all types satisfy traits_ex.isValueType 288 // _store[] = 0; // slightly faster than: memset(&_store, 0, _store.sizeof); 289 } 290 291 /// Returns: $(D true) if this has a defined value (is defined). 292 bool hasValue() const @safe nothrow @nogc 293 { 294 pragma(inline, true); 295 return _tix != _tix.init; 296 } 297 298 bool isNull() const @safe nothrow @nogc 299 { 300 pragma(inline, true); 301 return _tix == _tix.init; 302 } 303 304 size_t currentSize()() const @safe nothrow @nogc // template-lazy 305 { 306 if (isNull) 307 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 }