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, `pure nothrow @safe @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: https://github.com/Geod24/minivariant 28 * See_Also: http://forum.dlang.org/post/osfrjcuabwscvrecuvre@forum.dlang.org 29 * See_Also: https://forum.dlang.org/post/jfasmgwoffmbtuvrtxey@forum.dlang.org 30 * See_Also: https://forum.dlang.org/post/tdviqyzrcpttwwnvlzpv@forum.dlang.org 31 * See_Also: https://issues.dlang.org/show_bug.cgi?id=15399 32 */ 33 struct Algebraic(Types...) { 34 @safe: 35 36 alias Ix = ubyte; // type index type. TODO: use uint or size_t when there is room (depending on `memoryPacked`) 37 enum maxTypesCount = 2^^(Ix.sizeof * 8) - 1; // maximum number of allowed type parameters 38 39 import core.internal.traits : Unqual; /+ TODO: remove by using Andreis trick with `immutable` qualifier +/ 40 import std.meta : anySatisfy, allSatisfy, staticIndexOf; 41 import std.traits : StdCommonType = CommonType, hasIndirections, hasAliasing; 42 import nxt.traits_ex : isComparable, isEquable, sizesOf, stringsOf, allSame; 43 44 public: 45 46 enum name = Algebraic.stringof; 47 alias CommonType = StdCommonType!Types; 48 enum hasCommonType = !is(CommonType == void); 49 50 enum typeCount = Types.length; 51 enum typeSizes = sizesOf!Types; 52 enum typeNames = stringsOf!Types; 53 54 /// Is `true` iff `this` may have aliasing through any of `Types`. 55 enum mayHaveAliasing = anySatisfy!(hasAliasing, Types); 56 57 immutable static typeNamesRT = [typeNames]; // typeNames accessible at run-time, because `[typeNames]` is not @nogc 58 59 /// Is $(D true) if all $(D Types) stored in this $(D Algebraic) has the same length. 60 enum hasFixedSize = allSame!typeSizes; 61 62 private enum N = typeCount; // useful local shorthand 63 64 private enum indexOf(T) = staticIndexOf!(T, Types); /+ TODO: cast to ubyte if N is <= 256 +/ 65 66 // static checking 67 static assert(N >= 1, "No use storing zero types in a " ~ name); 68 static assert(N < maxTypesCount, 69 "Cannot store more than " ~ maxTypesCount.stringof ~ " Types in a " ~ name); 70 71 /** Is `true` if `U` is allowed to be assigned to `this`. */ 72 enum bool allowsAssignmentFrom(U) = ((N == 0 || 73 indexOf!(U) >= 0 || // either direct match or 74 ((!hasIndirections!U) && // no indirections and 75 indexOf!(Unqual!U) >= 0))); // ok to remove constness of value types 76 77 import nxt.maxsize_trait : maxSizeOf; 78 79 enum dataMaxSize = maxSizeOf!Types; 80 81 auto ref to(U)() const { 82 import std.conv : to; 83 final switch (typeIndex) { 84 foreach (const i, T; Types) { 85 case i: return as!T.to!U; 86 } 87 } 88 } 89 90 void toString(Sink)(ref scope Sink sink) const /*tlm*/ { 91 import std.conv : to; 92 if (!hasValue) 93 return sink("<Uninitialized Algebraic>"); 94 final switch (typeIndex) { 95 foreach (const i, T; Types) { 96 case i: 97 /+ TODO: use instead to avoid allocations +/ 98 // import std.format : formatValue; 99 // formatValue(sink, as!T); 100 sink(to!string(as!T)); 101 return; 102 } 103 } 104 } 105 106 /** Returns: $(D this) as a HTML-tagged $(D string). */ 107 @property void toHTML(Sink)(ref scope Sink sink) const /*tlm*/ { 108 // wrap information in HTML tags with CSS propertie 109 immutable tag = `dlang-` ~ typeName; 110 sink(`<`); sink(tag); sink(`>`); 111 toString(sink); 112 sink(`</`); sink(tag); sink(`>`); 113 } 114 115 pure: 116 117 /** Returns: Name (as a $(D string)) of Currently Stored Type. */ 118 private auto ref typeName()() const @safe nothrow @nogc /*tlm*/ { 119 pragma(inline, true); 120 return hasValue ? typeNamesRT[typeIndex] : null; 121 } 122 123 /** Copy construct from `that`. */ 124 this()(in Algebraic that) @safe nothrow @nogc { 125 _store = that._store; 126 _tix = that._tix; 127 pragma(msg, "Run postblits for " ~ Types.stringof); 128 } 129 130 /// Destruct. 131 ~this() nothrow @nogc { 132 pragma(inline, true); 133 if (hasValue) 134 release(); 135 } 136 137 /// Construct copy from `that`. 138 this(T)(T that) @trusted nothrow @nogc if (allowsAssignmentFrom!T) { 139 import core.lifetime : moveEmplace; 140 141 alias MT = Unqual!T; 142 static if (__traits(isPOD, MT)) 143 *cast(MT*)(&_store) = that; 144 else 145 moveEmplace(that, *cast(MT*)(&_store)); /+ TODO: ok when `that` has indirections? +/ 146 147 _tix = cast(Ix)(indexOf!MT + 1); // set type tag 148 } 149 150 Algebraic opAssign(T)(T that) @trusted nothrow @nogc if (allowsAssignmentFrom!T) { 151 import core.lifetime : moveEmplace; 152 153 if (hasValue) 154 release(); 155 156 alias MT = Unqual!T; 157 static if (__traits(isPOD, MT)) 158 *cast(MT*)(&_store) = that; 159 else 160 moveEmplace(that, *cast(MT*)(&_store)); /+ TODO: ok when `that` has indirections? +/ 161 162 _tix = cast(Ix)(indexOf!MT + 1); // set type tag 163 164 return this; 165 } 166 167 /** If the $(D Algebraic) object holds a value of the $(I exact) type $(D T), 168 returns a pointer to that value. Otherwise, returns $(D null). In cases 169 where $(D T) is statically disallowed, $(D peek) will not compile. 170 */ 171 @property inout(T)* peek(T)() inout @trusted nothrow @nogc { 172 pragma(inline, true); 173 alias MT = Unqual!T; 174 static if (!is(MT == void)) 175 static assert(allowsAssignmentFrom!MT, "Cannot store a " ~ MT.stringof ~ " in a " ~ name); 176 if (!ofType!MT) 177 return null; 178 return cast(inout MT*)&_store; /+ TODO: alignment +/ 179 } 180 181 /// Get Value of type $(D T). 182 @property auto ref inout(T) get(T)() inout @trusted { 183 version (LDC) pragma(inline, true); // DMD cannot inline 184 if (!ofType!T) 185 throw new AlgebraicException("Algebraic doesn't contain type"); 186 return as!T; 187 } 188 189 /// ditto 190 @property inout(Types[index]) get(uint index)() inout @safe if (index < Types.length) { 191 pragma(inline, true); 192 return get!(Types[index]); 193 } 194 195 /** Interpret data as type $(D T). 196 * 197 * See_Also: https://forum.dlang.org/post/thhrulbqsxbtzoyojqwx@forum.dlang.org 198 */ 199 private @property auto ref inout(T) as(T)() inout @system nothrow @nogc { 200 static if (_store.alignof >= T.alignof) 201 return *(cast(T*)&_store); 202 else { 203 inout(T) result; 204 (cast(ubyte*)&result)[0 .. T.sizeof] = _store[0 .. T.sizeof]; 205 return result; 206 } 207 } 208 209 /// Returns: $(D true) iff $(D this) $(D Algebraic) can store an instance of $(D T). 210 bool ofType(T)() const @safe nothrow @nogc { /+ TODO: shorter name such `isA`, `ofType` +/ 211 pragma(inline, true); 212 return _tix == indexOf!T + 1; 213 } 214 alias canStore = ofType; 215 alias isType = ofType; // minivariant compliance 216 217 /// Force $(D this) to the null/uninitialized/unset/undefined state. 218 void clear() @safe nothrow @nogc { 219 pragma(inline, true); 220 if (_tix != _tix.init) { 221 release(); 222 _tix = _tix.init; // this is enough to indicate undefined, no need to zero `_store` 223 } 224 } 225 /// ditto 226 alias nullify = clear; // compatible with `std.typecons.Nullable` 227 228 /// Nullable type support. 229 static immutable nullValue = typeof(this).init; 230 231 /// ditto 232 void opAssign(typeof(null)) { 233 pragma(inline, true); 234 clear(); 235 } 236 237 /// Release internal store. 238 private void release() @trusted nothrow @nogc { 239 import core.internal.traits : hasElaborateDestructor; 240 final switch (typeIndex) { 241 foreach (const i, T; Types) { 242 case i: 243 static if (hasElaborateDestructor!T) 244 .destroy(*cast(T*)&_store); // reinterpret 245 return; 246 } 247 case Ix.max: 248 return; 249 } 250 /+ TODO: don't call if all types satisfy traits_ex.isValueType +/ 251 // _store[] = 0; // slightly faster than: memset(&_store, 0, _store.sizeof); 252 } 253 254 /// Returns: $(D true) if this has a defined value (is defined). 255 bool hasValue() const @safe nothrow @nogc { 256 pragma(inline, true); 257 return _tix != _tix.init; 258 } 259 260 bool isNull() const @safe nothrow @nogc { 261 pragma(inline, true); 262 return _tix == _tix.init; 263 } 264 265 size_t currentSize()() const @safe nothrow @nogc /*tlm*/ { 266 if (isNull) 267 return 0; 268 final switch (typeIndex) { 269 foreach (const i, const typeSize; typeSizes) { 270 case i: 271 return typeSize; 272 } 273 } 274 } 275 276 /// Blindly Implicitly Convert Stored Value in $(D U). 277 private U convertTo(U)() const @trusted nothrow { 278 assert(hasValue); 279 final switch (typeIndex) { 280 foreach (const i, T; Types) { 281 case i: 282 return as!T; 283 } 284 } 285 } 286 287 static if (hasCommonType) { 288 CommonType commonValue() const @trusted pure nothrow @nogc { 289 assert(hasValue); 290 final switch (typeIndex) { 291 foreach (const i, T; Types) { 292 case i: 293 return cast(CommonType)as!T; 294 } 295 } 296 } 297 } 298 299 static if (allSatisfy!(isEquable, Types)) { 300 static if (hasCommonType) { 301 bool opEquals()(in Algebraic that) const @trusted nothrow @nogc /* tlm, opEquals is nothrow @nogc */ 302 { 303 if (_tix != that._tix) 304 return (this.convertTo!CommonType == 305 that.convertTo!CommonType); 306 if (!this.hasValue && 307 !that.hasValue) 308 return true; /+ TODO: same behaviour as floating point NaN? +/ 309 final switch (typeIndex) { 310 foreach (const i, T; Types) { 311 case i: 312 return this.as!T == that.as!T; 313 } 314 } 315 } 316 } else { 317 bool opEquals()(in Algebraic that) const @trusted nothrow /*tlm*/ 318 { 319 if (_tix != that._tix) 320 return false; // this needs to be nothrow or otherwise x in aa will throw which is not desirable 321 322 if (!this.hasValue && 323 !that.hasValue) 324 return true; /+ TODO: same behaviour as floating point NaN? +/ 325 326 final switch (typeIndex) { 327 foreach (const i, T; Types) { 328 case i: 329 return (this.as!T == 330 that.as!T); 331 } 332 } 333 334 assert(false); // this is for knet to compile but not in this module. TODO: remove when compiler is fixed 335 } 336 } 337 338 bool opEquals(T)(in T that) const @trusted nothrow { 339 /+ TODO: assert failure only if none of the Types isComparable to T +/ 340 static assert (allowsAssignmentFrom!T, 341 "Cannot equal any possible type of " ~ Algebraic.stringof ~ 342 " with " ~ T.stringof); 343 344 if (!ofType!T) 345 return false; // throw new AlgebraicException("Cannot equal Algebraic with current type " ~ "[Types][typeIndex]" ~ " with different types " ~ "T.stringof"); 346 return (this.as!T == that); 347 } 348 } 349 350 static if (allSatisfy!(isComparable, Types)) { 351 int opCmp()(in Algebraic that) const @trusted /* tlm, TODO: extend to Algebraic!(ThatTypes) */ 352 { 353 static if (hasCommonType) { /+ TODO: extend to haveCommonType!(Types, ThatTypes) +/ 354 if (_tix != that._tix) { 355 /+ TODO: functionize to defaultOpCmp to avoid postblits: +/ 356 const a = this.convertTo!CommonType; 357 const b = that.convertTo!CommonType; 358 return a < b ? -1 : a > b ? 1 : 0; 359 } 360 } else { 361 if (_tix != that._tix) 362 throw new AlgebraicException("Cannot compare Algebraic of type " ~ typeNamesRT[typeIndex] ~ 363 " with Algebraic of type " ~ typeNamesRT[that.typeIndex]); 364 } 365 366 final switch (typeIndex) { 367 foreach (const i, T; Types) { 368 case i: 369 /+ TODO: functionize to defaultOpCmp to avoid postblits: +/ 370 const a = this.as!T; 371 const b = that.as!T; 372 return a < b ? -1 : a > b ? 1 : 0; 373 } 374 } 375 } 376 377 int opCmp(U)(in U that) const @trusted { 378 /+ TODO: is CommonType or isComparable the correct way of checking this? +/ 379 static if (!is(StdCommonType!(Types, U) == void)) { 380 final switch (typeIndex) { 381 foreach (const i, T; Types) { 382 case i: 383 const a = this.as!T; 384 return a < that ? -1 : a > that ? 1 : 0; /+ TODO: functionize to defaultOpCmp +/ 385 } 386 } 387 } else { 388 static assert(allowsAssignmentFrom!U, /+ TODO: relax to allowsComparisonWith!U +/ 389 "Cannot compare " ~ Algebraic.stringof ~ " with " ~ U.stringof); 390 if (!ofType!U) 391 throw new AlgebraicException("Cannot compare " ~ Algebraic.stringof ~ " with " ~ U.stringof); 392 /+ TODO: functionize to defaultOpCmp to avoid postblits: +/ 393 const a = this.as!U; 394 return a < that ? -1 : a > that ? 1 : 0; 395 } 396 } 397 } 398 399 extern (D) hash_t toHash() const @trusted pure nothrow { 400 import core.internal.hash : hashOf; 401 const typeof(return) hash = _tix.hashOf; 402 if (hasValue) { 403 final switch (typeIndex) { 404 foreach (const i, T; Types) { 405 case i: return as!T.hashOf(hash); 406 } 407 } 408 } 409 return hash; 410 } 411 412 import std.digest : isDigest; 413 414 //+ TODO: use `!hasAliasing`? +/ 415 void toDigest(Digest)(scope ref Digest digest) const nothrow @nogc if (isDigest!Digest) { 416 import nxt.digestion : digestAny; 417 digestAny(digest, _tix); 418 if (hasValue) { 419 final switch (typeIndex) { 420 foreach (const i, T; Types) { 421 case i: 422 digestAny(digest, as!T); 423 return; 424 } 425 } 426 } 427 } 428 429 private: 430 // immutable to make hasAliasing!(Algebraic!(...)) false 431 union { 432 //align(8): 433 static if (mayHaveAliasing) { 434 ubyte[dataMaxSize] _store; 435 void* alignDummy; // non-packed means good alignment. TODO: check for maximum alignof of Types 436 } else { 437 // to please hasAliasing!(typeof(this)): 438 immutable(ubyte)[dataMaxSize] _store; 439 immutable(void)* alignDummy; // non-packed means good alignment. TODO: check for maximum alignof of Types 440 } 441 } 442 443 size_t typeIndex() const nothrow @nogc { 444 pragma(inline, true); 445 assert(_tix != 0, "Cannot get index from uninitialized (null) variant."); 446 return _tix - 1; 447 } 448 449 Ix _tix = 0; // type index 450 } 451 452 /// Algebraic type exception. 453 static class AlgebraicException : Exception { 454 this(string s) pure @nogc { 455 super(s); 456 } 457 } 458 459 unittest { 460 // Algebraic!(float, double, bool) a; 461 // a = 2.1; assert(a.to!string == "2.1"); assert(a.toHTML == "<dlang-double>2.1</dlang-double>"); 462 // a = 2.1f; assert(a.to!string == "2.1"); assert(a.toHTML == "<dlang-float>2.1</dlang-float>"); 463 // a = true; assert(a.to!string == "true"); assert(a.toHTML == "<dlang-bool>true</dlang-bool>"); 464 } 465 466 pure: 467 468 /// equality and comparison 469 @trusted nothrow @nogc unittest { 470 Algebraic!(float) a, b; 471 static assert(a.hasFixedSize); 472 473 a = 1.0f; 474 assert(a._tix != a.Ix.init); 475 476 b = 1.0f; 477 assert(b._tix != b.Ix.init); 478 479 assert(a._tix == b._tix); 480 assert((cast(ubyte*)&a)[0 .. a.sizeof] == (cast(ubyte*)&b)[0 .. b.sizeof]); 481 assert(a == b); /+ TODO: this errors with dmd master +/ 482 } 483 484 /// 485 nothrow @nogc unittest { 486 alias C = Algebraic!(float, double); 487 C a = 1.0; 488 const C b = 2.0; 489 const C c = 2.0f; 490 const C d = 1.0f; 491 492 assert(a.commonValue == 1); 493 assert(b.commonValue == 2); 494 assert(c.commonValue == 2); 495 assert(d.commonValue == 1); 496 497 // nothrow comparison possible 498 assert(a < b); 499 assert(a < c); 500 assert(a == d); 501 502 static assert(!a.hasFixedSize); 503 static assert(a.allowsAssignmentFrom!float); 504 static assert(a.allowsAssignmentFrom!double); 505 static assert(!a.allowsAssignmentFrom!string); 506 507 a.clear(); 508 assert(!a.hasValue); 509 assert(a.peek!float is null); 510 assert(a.peek!double is null); 511 assert(a.currentSize == 0); 512 } 513 514 /// aliasing traits 515 nothrow @nogc unittest { 516 import std.traits : hasAliasing; 517 static assert(!hasAliasing!(Algebraic!(long, double))); 518 static assert(!hasAliasing!(Algebraic!(long, string))); 519 static assert(!hasAliasing!(Algebraic!(long, immutable(double)*))); 520 static assert(hasAliasing!(Algebraic!(long, double*))); 521 } 522 523 nothrow @nogc unittest { 524 alias V = Algebraic!(long, double); 525 const a = V(1.0); 526 527 static assert(a.hasFixedSize); 528 529 assert(a.ofType!double); 530 assert(a.peek!long is null); 531 assert(a.peek!double !is null); 532 533 static assert(is(typeof(a.peek!long) == const(long)*)); 534 static assert(is(typeof(a.peek!double) == const(double)*)); 535 } 536 537 /// equality and comparison 538 nothrow @nogc unittest { 539 Algebraic!(int) a, b; 540 static assert(a.hasFixedSize); 541 a = 1; 542 b = 1; 543 assert(a == b); 544 } 545 546 /// equality and comparison 547 nothrow @nogc unittest { 548 Algebraic!(float) a, b; 549 static assert(a.hasFixedSize); 550 a = 1.0f; 551 b = 1.0f; 552 assert(a == b); 553 } 554 555 /// equality and comparison 556 /*TODO: @nogc*/ unittest { 557 Algebraic!(float, double, string) a, b; 558 559 static assert(!a.hasFixedSize); 560 561 a = 1.0f; 562 b = 1.0f; 563 assert(a == b); 564 565 a = 1.0f; 566 b = 2.0f; 567 assert(a != b); 568 assert(a < b); 569 assert(b > a); 570 571 a = "alpha"; 572 b = "alpha"; 573 assert(a == b); 574 575 a = "a"; 576 b = "b"; 577 assert(a != b); 578 assert(a < b); 579 assert(b > a); 580 } 581 582 /// AA keys 583 nothrow unittest { 584 alias C = Algebraic!(float, double); 585 static assert(!C.hasFixedSize); 586 string[C] a; 587 a[C(1.0f)] = "1.0f"; 588 a[C(2.0)] = "2.0"; 589 assert(a[C(1.0f)] == "1.0f"); 590 assert(a[C(2.0)] == "2.0"); 591 } 592 593 /// verify nothrow comparisons 594 nothrow @nogc unittest { 595 alias C = Algebraic!(int, float, double); 596 static assert(!C.hasFixedSize); 597 assert(C(1.0) < 2); 598 assert(C(1.0) < 2.0); 599 assert(C(1.0) < 2.0); 600 static assert(!__traits(compiles, { C(1.0) < 'a'; })); // cannot compare with char 601 static assert(!__traits(compiles, { C(1.0) < "a"; })); // cannot compare with string 602 } 603 604 /// TODO 605 nothrow @nogc unittest { 606 // alias C = Algebraic!(int, float, double); 607 // alias D = Algebraic!(float, double); 608 // assert(C(1) < D(2.0)); 609 // assert(C(1) < D(1.0)); 610 // static assert(!__traits(compiles, { C(1.0) < "a"; })); // cannot compare with string 611 } 612 613 /// if types have CommonType comparison is nothrow @nogc 614 nothrow @nogc unittest { 615 alias C = Algebraic!(short, int, long, float, double); 616 static assert(!C.hasFixedSize); 617 assert(C(1) != C(2.0)); 618 assert(C(1) == C(1.0)); 619 } 620 621 /// if types have `CommonType` then comparison is `nothrow @nogc` 622 nothrow @nogc unittest { 623 alias C = Algebraic!(short, int, long, float, double); 624 static assert(!C.hasFixedSize); 625 assert(C(1) != C(2.0)); 626 assert(C(1) == C(1.0)); 627 } 628 629 nothrow @nogc unittest { 630 alias C = Algebraic!(int, string); 631 static assert(!C.hasFixedSize); 632 C x; 633 x = 42; 634 } 635 636 nothrow @nogc unittest { 637 alias C = Algebraic!(int); 638 static assert(C.hasFixedSize); 639 C x; 640 x = 42; 641 } 642 643 unittest { 644 import core.internal.traits : hasElaborateCopyConstructor; 645 646 import std.exception : assertThrown; 647 648 static assert(hasElaborateCopyConstructor!(char[2]) == false); 649 static assert(hasElaborateCopyConstructor!(char[]) == false); 650 651 // static assert(Algebraic!(char, wchar).sizeof == 2 + 1); 652 // static assert(Algebraic!(wchar, dchar).sizeof == 4 + 1); 653 // static assert(Algebraic!(long, double).sizeof == 8 + 1); 654 // static assert(Algebraic!(int, float).sizeof == 4 + 1); 655 // static assert(Algebraic!(char[2], wchar[2]).sizeof == 2 * 2 + 1); 656 657 alias C = Algebraic!(string, 658 // fixed length strings: small string optimizations (SSOs) 659 int, float, 660 long, double); 661 static assert(!C.hasFixedSize); 662 663 static assert(C.allowsAssignmentFrom!int); 664 static assert(!C.allowsAssignmentFrom!(int[2])); 665 static assert(C.allowsAssignmentFrom!(const(int))); 666 667 static assert(C.dataMaxSize == string.sizeof); 668 static assert(!__traits(compiles, { assert(d == 'a'); })); 669 670 assert(C() == C()); // two undefined are equal 671 672 C d; 673 C e = d; // copy construction 674 assert(e == d); // two undefined should not equal 675 676 d = 11; 677 assert(d != e); 678 679 /+ TODO: Allow this d = cast(ubyte)255; +/ 680 681 d = 1.0f; 682 assertThrown!AlgebraicException(d.get!double); 683 assert(d.hasValue); 684 assert(d.ofType!float); 685 assert(d.peek!float !is null); 686 assert(!d.ofType!double); 687 assert(d.peek!double is null); 688 assert(d.get!float == 1.0f); 689 assert(d == 1.0f); 690 assert(d != 2.0f); 691 assert(d < 2.0f); 692 assert(d != "2.0f"); 693 assertThrown!AlgebraicException(d < 2.0); 694 assertThrown!AlgebraicException(d < "2.0"); 695 assert(d.currentSize == float.sizeof); 696 697 d = 2; 698 assert(d.hasValue); 699 assert(d.peek!int !is null); 700 assert(!d.ofType!float); 701 assert(d.peek!float is null); 702 assert(d.get!int == 2); 703 assert(d == 2); 704 assert(d != 3); 705 assert(d < 3); 706 assertThrown!AlgebraicException(d < 2.0f); 707 assertThrown!AlgebraicException(d < "2.0"); 708 assert(d.currentSize == int.sizeof); 709 710 d = "abc"; 711 assert(d.hasValue); 712 assert(d.get!0 == "abc"); 713 assert(d.get!string == "abc"); 714 assert(d.ofType!string); 715 assert(d.peek!string !is null); 716 assert(d == "abc"); 717 assert(d != "abcd"); 718 assert(d < "abcd"); 719 assertThrown!AlgebraicException(d < 2.0f); 720 assertThrown!AlgebraicException(d < 2.0); 721 assert(d.currentSize == string.sizeof); 722 723 d = 2.0; 724 assert(d.hasValue); 725 assert(d.get!double == 2.0); 726 assert(d.ofType!double); 727 assert(d.peek!double !is null); 728 assert(d == 2.0); 729 assert(d != 3.0); 730 assert(d < 3.0); 731 assertThrown!AlgebraicException(d < 2.0f); 732 assertThrown!AlgebraicException(d < "2.0"); 733 assert(d.currentSize == double.sizeof); 734 735 d.clear(); 736 assert(d.peek!int is null); 737 assert(d.peek!float is null); 738 assert(d.peek!double is null); 739 assert(d.peek!string is null); 740 assert(!d.hasValue); 741 assert(d.currentSize == 0); 742 743 assert(C(1.0f) == C(1.0f)); 744 assert(C(1.0f) < C(2.0f)); 745 assert(C(2.0f) > C(1.0f)); 746 747 assertThrown!AlgebraicException(C(1.0f) < C(1.0)); 748 // assertThrown!AlgebraicException(C(1.0f) == C(1.0)); 749 } 750 751 /// 752 nothrow @nogc unittest { 753 import nxt.container.static_array : MutableStringN; 754 alias String15 = MutableStringN!(15); 755 756 String15 s; 757 String15 t = s; 758 assert(t == s); 759 760 alias V = Algebraic!(String15, string); 761 V v = String15("first"); 762 assert(v.peek!String15); 763 assert(!v.peek!string); 764 765 v = String15("second"); 766 assert(v.peek!String15); 767 assert(!v.peek!string); 768 769 v = "third"; 770 assert(!v.peek!String15); 771 assert(v.peek!string); 772 773 auto w = v; 774 assert(v == w); 775 w.clear(); 776 assert(!v.isNull); 777 assert(w.isNull); 778 w = v; 779 assert(!w.isNull); 780 781 v = V.init; 782 assert(v == V.init); 783 } 784 785 /// check default values 786 nothrow @nogc unittest { 787 import nxt.container.static_array : MutableStringN; 788 alias String15 = MutableStringN!(15); 789 790 alias V = Algebraic!(String15, string); 791 V _; 792 assert(_._tix == V.Ix.init); 793 assert(V.init._tix == V.Ix.init); 794 795 /+ TODO: import nxt.bit_traits : isInitAllZeroBits; +/ 796 /+ TODO: static assert(isInitAllZeroBits!(V)); +/ 797 }