1 module memutils.vector; 2 3 import std.algorithm : swap, initializeAll; 4 import std.range : empty; 5 import std.traits; 6 import core.stdc..string; 7 import std.range : isInputRange, isForwardRange, isRandomAccessRange, ElementType, refRange, RefRange, hasLength; 8 import core.exception : RangeError; 9 import std.exception : enforce; 10 import memutils.allocators; 11 import memutils.helpers; 12 import memutils.utils; 13 import memutils.refcounted; 14 import std.conv : emplace, to; 15 16 template isImplicitlyConvertibleLegacy(From, To) 17 { 18 enum bool isImplicitlyConvertibleLegacy = is(typeof({ 19 void fun(ref From v) 20 { 21 void gun(To) {} 22 gun(v); 23 } 24 })); 25 } 26 27 28 public alias SecureArray(T) = Array!(T, SecureMem); 29 30 template Array(T, ALLOC = ThreadMem) 31 if (!is (T == RefCounted!(Vector!(T, ALLOC), ALLOC))) 32 { 33 alias Array = RefCounted!(Vector!(T, ALLOC), ALLOC); 34 } 35 36 public alias SecureVector(T) = Vector!(T, SecureMem); 37 // TODO: Remove implicit string casting for Vector!ubyte! Encourage use of Vector!char [].idup instead. 38 39 /// An array that uses a custom allocator. 40 struct Vector(T, ALLOC = ThreadMem) 41 { 42 @disable this(this); 43 static if (!is(ALLOC == AppMem)) enum NOGC = true; 44 void opAssign()(auto ref Vector!(T, ALLOC) other) { 45 if (other.ptr !is this.ptr) 46 this.swap(other); 47 } 48 49 void opAssign()(ref T[] other) { 50 this[] = other[]; 51 } 52 53 // Payload cannot be copied 54 private struct Payload 55 { 56 size_t _capacity; 57 T[] _payload; 58 59 // Convenience constructor 60 this()(auto ref T[] p) 61 { 62 if (p.length == 0) return; 63 64 length = p.length; 65 66 static if (isImplicitlyConvertibleLegacy!(T, T)) { 67 if (_payload.ptr is p.ptr && _payload.length == p.length) { 68 p = null; 69 } 70 else { 71 _payload[] = p[0 .. $]; // todo: use emplace? 72 } 73 } 74 else 75 { 76 memmove(_payload.ptr, p.ptr, T.sizeof*p.length); 77 } 78 } 79 80 // Destructor releases array memory 81 ~this() const nothrow 82 { 83 try { 84 if (_capacity == 0 || _payload.ptr is null) 85 return; 86 T[] data = cast(T[]) _payload.ptr[0 .. _capacity]; 87 freeArray!(T, ALLOC)(data, _payload.length); // calls destructors and frees memory 88 } 89 catch (Throwable e) { assert(false, "Vector.~this Exception: " ~ e.toString()); } 90 } 91 92 void opAssign(Payload rhs) 93 { 94 assert(false); 95 } 96 97 // Duplicate data 98 // @property Payload dup() 99 // { 100 // Payload result; 101 // result._payload = _payload.dup; 102 // // Conservatively assume initial capacity == length 103 // result._capacity = result._payload.length; 104 // return result; 105 // } 106 107 // length 108 @property size_t length() const 109 { 110 return _payload.length; 111 } 112 113 // length 114 @property void length(size_t newLength) 115 { 116 if (length > 0 && length >= newLength) 117 { 118 // shorten 119 static if (hasElaborateDestructor!T) { 120 foreach (ref e; _payload.ptr[newLength .. _payload.length]) { 121 static if (is(T == struct) && !isPointer!T) // call destructors but not for indirections... 122 .destroy(e); 123 } 124 125 // Zero out unused capacity to prevent gc from seeing 126 // false pointers 127 static if (hasIndirections!T) 128 memset(_payload.ptr + newLength, 0, (_payload.length - newLength) * T.sizeof); 129 } 130 _payload = _payload.ptr[0 .. newLength]; 131 return; 132 } 133 134 if (newLength > 0) { 135 // enlarge 136 auto startEmplace = length; 137 reserve(newLength); 138 _payload = _payload.ptr[0 .. newLength]; 139 static if (!isImplicitlyConvertibleLegacy!(T, T)) { 140 T t = T(); 141 foreach (size_t i; startEmplace .. length) 142 memmove(_payload.ptr + i, &t, T.sizeof); 143 144 } else 145 initializeAll(_payload.ptr[startEmplace .. length]); 146 } 147 } 148 149 // capacity 150 @property size_t capacity() const 151 { 152 return _capacity; 153 } 154 155 // reserve 156 void reserve(size_t elements) 157 { 158 if (elements <= capacity) return; 159 // TODO: allow vector to become smaller? 160 161 TRACE("Reserve ", length, " => ", elements, " elements."); 162 163 if (_capacity > 0) { 164 size_t len = _payload.length; 165 _payload = _payload.ptr[0 .. _capacity]; 166 _payload = reallocArray!(T, ALLOC)(_payload, elements)[0 .. len]; 167 } 168 else if (elements > 0) { 169 _payload = allocArray!(T, ALLOC)(elements)[0 .. _payload.length]; 170 } 171 _capacity = elements; 172 } 173 174 static if (is(T == char)) 175 pragma(inline, true) size_t pushBack(Stuff)(Stuff stuff) 176 if (is(Stuff == char[]) || is(Stuff == string)) 177 { 178 TRACE("Vector.append @disabled this(this)"); 179 if (_capacity <= length + stuff.length) 180 { 181 reserve(1 + (length + stuff.length) * 3 / 2); 182 } 183 assert(capacity >= length + stuff.length && _payload.ptr); 184 185 memmove(_payload.ptr + _payload.length, stuff.ptr, stuff.length); 186 _payload = _payload.ptr[0 .. _payload.length + stuff.length]; 187 188 return 1; 189 } 190 191 pragma(inline, true) size_t pushBack(Stuff)(auto ref Stuff stuff) 192 if (!isImplicitlyConvertibleLegacy!(T, T) && is(T == Stuff)) 193 { 194 TRACE("Vector.append @disabled this(this)"); 195 if (_capacity == length) 196 { 197 reserve(1 + capacity * 3 / 2); 198 } 199 assert(capacity > length && _payload.ptr); 200 201 T* t = &stuff; 202 203 memmove(_payload.ptr + _payload.length, t, T.sizeof); 204 memset(t, 0, T.sizeof); 205 _payload = _payload.ptr[0 .. _payload.length + 1]; 206 207 return 1; 208 } 209 210 // Insert one item 211 pragma(inline, true) size_t pushBack(Stuff)(auto ref Stuff stuff) 212 if (isImplicitlyConvertibleLegacy!(T, T) && isImplicitlyConvertibleLegacy!(Stuff, T)) 213 { 214 TRACE("Vector.append"); 215 //logTrace("Capacity: ", _capacity, " length: ", length); 216 if (_capacity == length) 217 { 218 reserve(1 + capacity * 3 / 2); 219 } 220 assert(capacity > length && _payload.ptr, "Payload pointer " ~ _payload.ptr.to!string ~ "'s capacity: " ~ capacity.to!string ~ " must be more than " ~ length.to!string); 221 emplace(_payload.ptr + _payload.length, stuff); 222 _payload = _payload.ptr[0 .. _payload.length + 1]; 223 return 1; 224 } 225 226 /// Insert a range of items 227 pragma(inline, true) size_t pushBack(Stuff)(auto ref Stuff stuff) 228 if (isInputRange!Stuff && (isImplicitlyConvertibleLegacy!(ElementType!Stuff, T) || is(T == ElementType!Stuff))) 229 { 230 TRACE("Vector.append 2"); 231 static if (hasLength!Stuff) 232 { 233 immutable oldLength = length; 234 reserve(oldLength + stuff.length); 235 } 236 size_t result; 237 foreach (ref item; stuff) 238 { 239 pushBack(item); 240 ++result; 241 } 242 static if (hasLength!Stuff) 243 { 244 assert(length == oldLength + stuff.length); 245 } 246 return result; 247 } 248 } 249 250 private alias Data = Payload; 251 private Data _data; 252 253 this(size_t elms) { 254 resize(elms); 255 } 256 257 /** 258 Constructor taking a number of items 259 */ 260 this(U)(U[] values...) 261 if (isImplicitlyConvertibleLegacy!(U, T)) 262 { 263 // TODO: This doesn't work with refcounted 264 _data = Data(cast(T[])values); 265 } 266 267 /** 268 Constructor taking an input range 269 */ 270 this(Stuff)(Stuff stuff) 271 if (isInputRange!Stuff && isImplicitlyConvertibleLegacy!(UnConst!(ElementType!Stuff), T) && !is(Stuff == T[])) 272 { 273 insertBack(stuff); 274 } 275 276 /** 277 * Move Constructor 278 */ 279 this()(auto ref typeof(this) other) { 280 if (this.ptr !is other.ptr) 281 this.swap(other); 282 else { 283 typeof(this) ini; 284 other.swap(ini); 285 } 286 } 287 288 /** 289 Duplicates the container. The elements themselves are not transitively 290 duplicated. 291 292 Complexity: $(BIGOH n). 293 */ 294 @property Vector!(T, ALLOC) dup() const 295 { 296 static if (__traits(compiles, { T a; T b; a = b; } ())) { 297 auto ret = Vector!(T, ALLOC)(cast(T[])_data._payload); 298 return ret.move; 299 } 300 else static if (__traits(hasMember, T, "dup")) // Element is @disable this(this) but has dup() 301 { 302 Vector!(T, ALLOC) vec = Vector!(T, ALLOC)(length); 303 // swap each element with a duplicate 304 foreach (size_t i, ref el; _data._payload) { 305 T t = el.dup; 306 memmove(vec._data._payload.ptr + i, &t, T.sizeof); 307 memset(&t, 0, T.sizeof); 308 } 309 return vec.move(); 310 } else static assert(false, "Cannot dup() the element: " ~ T.stringof); 311 } 312 313 /// ditto 314 @property RefCounted!(Vector!(T, ALLOC), ALLOC) dupr() const 315 { 316 return RefCounted!(Vector!(T, ALLOC), ALLOC)(cast(T[])_data._payload); 317 } 318 319 void swap(ref Vector!(T, ALLOC) other) { 320 import std.algorithm : swap; 321 .swap(_data._payload, other._data._payload); 322 .swap(_data._capacity, other._data._capacity); 323 } 324 void swap(T[] other) { 325 this[] = other; 326 } 327 328 @property Vector!(T, ALLOC) move() { 329 return Vector!(T, ALLOC)(this); 330 } 331 332 /** 333 Property returning $(D true) if and only if the container has no 334 elements. 335 336 Complexity: $(BIGOH 1) 337 */ 338 @property bool empty() const 339 { 340 return !_data._payload || _data._payload.empty; 341 } 342 343 /** 344 Returns the number of elements in the container. 345 346 Complexity: $(BIGOH 1). 347 */ 348 @property size_t length() const 349 { 350 return _data._payload.length; 351 } 352 353 /// ditto 354 size_t opDollar() const 355 { 356 return length; 357 } 358 359 @property T* ptr() inout { 360 return cast(T*) _data._payload.ptr; 361 } 362 363 @property T* end() inout { 364 return this.ptr + this.length; 365 } 366 367 /** 368 Returns the maximum number of elements the container can store without 369 (a) allocating memory, (b) invalidating iterators upon insertion. 370 371 Complexity: $(BIGOH 1) 372 */ 373 @property size_t capacity() const 374 { 375 return _data._capacity; 376 } 377 378 /* 379 @property auto range() { 380 return refRange(&_data._payload); 381 } 382 */ 383 384 /** 385 Ensures sufficient capacity to accommodate $(D e) elements. 386 387 Postcondition: $(D capacity >= e) 388 389 Complexity: $(BIGOH 1) 390 */ 391 void reserve(size_t elements) 392 { 393 _data.reserve(elements); 394 } 395 396 /** 397 Returns an array that can be translated to a range using ($D refRange). 398 399 Complexity: $(BIGOH 1) 400 */ 401 auto opSlice() inout 402 { 403 //static if (is(T[] == ubyte[])) 404 // return cast(string) _data._payload; 405 //else 406 return *cast(T[]*)&_data._payload; 407 } 408 409 /** 410 Returns an array of the container from index $(D a) up to (excluding) index $(D b). 411 412 Precondition: $(D a <= b && b <= length) 413 414 Complexity: $(BIGOH 1) 415 */ 416 T[] opSlice(size_t i, size_t j) const 417 { 418 version (assert) if (i > j || j > length) throw new RangeError(); 419 return (cast(T[])_data._payload)[i .. j]; 420 } 421 422 /** 423 Forward to $(D opSlice().front) and $(D opSlice().back), respectively. 424 425 Precondition: $(D !empty) 426 427 Complexity: $(BIGOH 1) 428 */ 429 @property ref T front() 430 { 431 return _data._payload[0]; 432 } 433 434 /// ditto 435 @property ref T back() 436 { 437 return _data._payload[$ - 1]; 438 } 439 440 /** 441 Indexing operators yield or modify the value at a specified index. 442 443 Precondition: $(D i < length) 444 445 Complexity: $(BIGOH 1) 446 */ 447 ref T opIndex(size_t i) const 448 { 449 return *cast(T*)&_data._payload[i]; 450 } 451 452 void opIndexAssign(U)(auto ref U val, size_t i) 453 { 454 static if (__traits(compiles, {_data._payload[i] = cast(T) val; }())) 455 _data._payload[i] = cast(T) val; 456 else { // swap 457 memmove(_data._payload.ptr + i, &val, U.sizeof); 458 memset(&val, 0, U.sizeof); 459 } 460 } 461 /** 462 Slicing operations execute an operation on an entire slice. 463 464 Precondition: $(D i < j && j < length) 465 466 Complexity: $(BIGOH slice.length) 467 */ 468 void opSliceAssign(Stuff)(auto ref Stuff value) 469 { 470 static if (isRandomAccessRange!Stuff) 471 { 472 _data.length = value.length; 473 _data._payload.ptr[0 .. value.length] = value[0 .. $]; 474 } else static if (is(UnConst!Stuff == Vector!(T, ALLOC))) { 475 _data.length = value._data.length; 476 _data._payload[] = value._data._payload[]; 477 } 478 else static if (isImplicitlyConvertibleLegacy!(T, ElementType!Stuff)) { 479 _data.length = value.length; 480 _data._payload[] = cast(T[]) value; 481 } else static assert(false, "Can't convert " ~ Stuff.stringof ~ " to " ~ T.stringof ~ "[]"); 482 } 483 484 /// ditto 485 void opSliceAssign(Stuff)(Stuff value, size_t i, size_t j) 486 { 487 auto slice = _data._payload; 488 slice[i .. j] = value; 489 } 490 491 /// ditto 492 void opSliceUnary(string op)() 493 if(op == "++" || op == "--") 494 { 495 mixin(op~"_data._payload[];"); 496 } 497 498 /// ditto 499 void opSliceUnary(string op)(size_t i, size_t j) 500 if(op == "++" || op == "--") 501 { 502 mixin(op~"slice[i .. j];"); 503 } 504 505 /// ditto 506 void opSliceOpAssign(string op)(T value) 507 { 508 mixin("_data._payload[] "~op~"= value;"); 509 } 510 511 /// ditto 512 void opSliceOpAssign(string op)(T value, size_t i, size_t j) 513 { 514 mixin("slice[i .. j] "~op~"= value;"); 515 } 516 517 /** 518 Returns a new container that's the concatenation of $(D this) and its 519 argument. $(D opBinaryRight) is only defined if $(D Stuff) does not 520 define $(D opBinary). 521 522 Complexity: $(BIGOH n + m), where m is the number of elements in $(D 523 stuff) 524 */ 525 auto opBinary(string op, Stuff)(Stuff stuff) 526 if (op == "~") 527 { 528 TRACE("Appending stuff"); 529 RefCounted!(Vector!(T, ALLOC), ALLOC) result; 530 // @@@BUG@@ result ~= this[] doesn't work 531 auto r = this[]; 532 result ~= r; 533 assert(result.length == length); 534 result ~= stuff[]; 535 return result; 536 } 537 538 void opOpAssign(string op, U)(auto ref U input) 539 if (op == "^") 540 { 541 if (this.length < input.length) 542 this.resize(input.length); 543 544 pure static void xorBuf(T)(T* output, const(T)* input, size_t length) 545 { 546 while (length >= 8) 547 { 548 output[0 .. 8] ^= input[0 .. 8]; 549 550 output += 8; input += 8; length -= 8; 551 } 552 553 output[0 .. length] ^= input[0 .. length]; 554 } 555 556 xorBuf(this.ptr, input.ptr, input.length); 557 } 558 559 /** 560 Forwards to $(D pushBack(stuff)). 561 */ 562 pragma(inline, true) void opOpAssign(string op, Stuff)(auto ref Stuff stuff) 563 if (op == "~") 564 { 565 static if (is (Stuff == RefCounted!(typeof(this)))) { 566 insertBack(cast(T[]) stuff[]); 567 } 568 else static if (is (Stuff == typeof(this))) { 569 insertBack(cast(T[]) stuff[]); 570 } 571 else 572 { 573 insertBack(stuff); 574 } 575 } 576 577 /** 578 Removes all contents from the container. The container decides how $(D 579 capacity) is affected. 580 581 Postcondition: $(D empty) 582 583 Complexity: $(BIGOH n) 584 */ 585 void clear() 586 { 587 TRACE("Vector.clear()"); 588 _data.length = 0; 589 } 590 591 592 /** 593 Sets the number of elements in the container to $(D newSize). If $(D 594 newSize) is greater than $(D length), the added elements are added to 595 unspecified positions in the container and initialized with $(D 596 T.init). 597 598 Complexity: $(BIGOH abs(n - newLength)) 599 600 Postcondition: $(D length == newLength) 601 */ 602 pragma(inline, true) @property void length(size_t newLength) 603 { 604 _data.length = newLength; 605 } 606 607 pragma(inline, true) void resize(size_t newLength) 608 { 609 this.length = newLength; 610 } 611 612 import std.traits : isNumeric; 613 614 int opCmp(Alloc)(auto const ref Vector!(T, Alloc) other) const 615 { 616 if (this[] == other[]) 617 return 0; 618 else if (this[] < other[]) 619 return -1; 620 else 621 return 0; 622 } 623 624 pragma(inline, true) size_t pushBack(Stuff...)(Stuff stuff) 625 if (!isNumeric!Stuff || !is ( T == ubyte )) 626 { 627 return insertBack(stuff); 628 } 629 630 pragma(inline, true) size_t pushBack(Stuff...)(Stuff stuff) 631 if (isNumeric!Stuff && is(T == ubyte)) 632 { 633 return insertBack(cast(T) stuff); 634 } 635 636 pragma(inline, true) size_t insert(Stuff...)(Stuff stuff) { 637 return insertBack(stuff); 638 } 639 640 /** 641 Inserts $(D value) to the front or back of the container. $(D stuff) 642 can be a value convertible to $(D T) or a range of objects convertible 643 to $(D T). The stable version behaves the same, but guarantees that 644 ranges iterating over the container are never invalidated. 645 646 Returns: The number of elements inserted 647 648 Complexity: $(BIGOH m * log(n)), where $(D m) is the number of 649 elements in $(D stuff) 650 */ 651 pragma(inline, true) size_t insertBack(Stuff)(auto ref Stuff stuff) 652 { 653 static if (isImplicitlyConvertibleLegacy!(Stuff, T[])) 654 return _data.pushBack(cast(T[])stuff); 655 else static if (isSomeString!(Stuff) && !isImplicitlyConvertibleLegacy!(Stuff, T)) { 656 return _data.pushBack(cast(T[])stuff); 657 } 658 else static if (isSomeString!(Stuff) && isImplicitlyConvertibleLegacy!(Stuff, T)) { 659 return _data.pushBack(cast(T)stuff); 660 } 661 else static if (isInputRange!(Stuff) && isImplicitlyConvertibleLegacy!(ForeachType!Stuff, T)) { 662 return _data.pushBack(stuff); 663 } 664 else 665 return _data.pushBack(cast(T) stuff); 666 } 667 668 /** 669 Removes the value at the back of the container. The stable version 670 behaves the same, but guarantees that ranges iterating over the 671 container are never invalidated. 672 673 Precondition: $(D !empty) 674 675 Complexity: $(BIGOH log(n)). 676 */ 677 void removeBack() 678 { 679 enforce(!empty); 680 static if (hasElaborateDestructor!T) 681 .destroy(_data._payload[$ - 1]); 682 683 _data._payload = _data._payload[0 .. $ - 1]; 684 } 685 686 @trusted 687 void removeFront() { 688 689 enforce(!empty); 690 static if (hasElaborateDestructor!T) 691 .destroy(_data._payload[0]); 692 if (_data._payload.length > 1) { 693 memmove(_data._payload.ptr, _data._payload.ptr + 1, T.sizeof * (_data._payload.length - 1)); 694 memset(_data._payload.ptr + _data._payload.length - 1, 0, T.sizeof); 695 } 696 _data._payload.length -= 1; 697 } 698 699 /** 700 Removes $(D howMany) values at the front or back of the 701 container. Unlike the unparameterized versions above, these functions 702 do not throw if they could not remove $(D howMany) elements. Instead, 703 if $(D howMany > n), all elements are removed. The returned value is 704 the effective number of elements removed. The stable version behaves 705 the same, but guarantees that ranges iterating over the container are 706 never invalidated. 707 708 Returns: The number of elements removed 709 710 Complexity: $(BIGOH howMany). 711 */ 712 size_t removeBack(size_t howMany) 713 { 714 if (howMany > length) howMany = length; 715 static if (hasElaborateDestructor!T) 716 foreach (ref e; _data._payload[$ - howMany .. $]) 717 .destroy(e); 718 719 _data._payload = _data._payload[0 .. $ - howMany]; 720 return howMany; 721 } 722 723 /** 724 Inserts $(D stuff) before position i. 725 726 Returns: The number of values inserted. 727 728 Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff) 729 */ 730 void insertBefore(Stuff)(size_t i, Stuff stuff) 731 if (isImplicitlyConvertibleLegacy!(Stuff, T)) 732 { 733 enforce(i <= length); 734 reserve(length + 1); 735 736 // Move elements over by one slot 737 memmove(_data._payload.ptr + i + 1, 738 _data._payload.ptr + i, 739 T.sizeof * (length - i)); 740 emplace(_data._payload.ptr + i, stuff); 741 _data._payload = _data._payload.ptr[0 .. _data._payload.length + 1]; 742 } 743 744 /// ditto 745 size_t insertBefore(Stuff)(size_t i, Stuff stuff) 746 if (isInputRange!Stuff && isImplicitlyConvertibleLegacy!(ElementType!Stuff, T)) 747 { 748 enforce(i <= length); 749 static if (isForwardRange!Stuff) 750 { 751 // Can find the length in advance 752 auto extra = walkLength(stuff); 753 if (!extra) return 0; 754 reserve(length + extra); 755 // Move elements over by extra slots 756 memmove(_data._payload.ptr + i + extra, 757 _data._payload.ptr + i, 758 T.sizeof * (length - i)); 759 foreach (p; _data._payload.ptr + i .. 760 _data._payload.ptr + i + extra) 761 { 762 emplace(p, stuff.front); 763 stuff.popFront(); 764 } 765 _data._payload = _data._payload.ptr[0 .. _data._payload.length + extra]; 766 return extra; 767 } 768 else 769 { 770 enforce(_data); 771 immutable offset = i; 772 enforce(offset <= length); 773 auto result = pushBack(stuff); 774 bringToFront(this[offset .. length - result], 775 this[length - result .. length]); 776 return result; 777 } 778 } 779 780 /// ditto 781 size_t insertAfter(Stuff)(size_t i, Stuff stuff) 782 { 783 enforce(r._outer._data is _data); 784 // TODO: optimize 785 immutable offset = i; 786 enforce(offset <= length); 787 auto result = pushBack(stuff); 788 bringToFront(this[offset .. length - result], 789 this[length - result .. length]); 790 return result; 791 } 792 793 bool opEquals()(auto const ref RefCounted!(Vector!(T, ALLOC), ALLOC) other_) const { 794 import memutils.constants : logTrace; 795 if (other_.empty && empty()) 796 return true; 797 else if (other_.empty) 798 return false; 799 if (other_.length != length) 800 return false; 801 // TODO: use the true types 802 return _data._payload[0 .. length] == other_._data._payload[0 .. length]; 803 } 804 805 bool opEquals()(auto const ref Vector!(T, ALLOC) other_) const { 806 if (other_.empty && empty()) 807 return true; 808 else if (other_.empty) 809 return false; 810 if (other_.length != length) 811 return false; 812 813 return _data._payload[0 .. length] == other_._data._payload[0 .. length]; 814 } 815 816 bool opEquals()(auto const ref T[] other) { 817 logTrace("other: ", other, " this: ", _data._payload); 818 return other == _data._payload; 819 } 820 821 } 822 823 auto array(T)(T[] val) 824 { 825 return Array!(Unqual!T)(val); 826 } 827 828 auto vector(T)(T[] val) 829 { 830 return Vector!(Unqual!T)(val); 831 } 832 833 void TRACE(T...)(T t) { 834 //import std.stdio : writeln; 835 //writeln(t); 836 }