1 module nxt.dynamic_array; 2 3 // version = debugCtors; 4 5 import core.internal.traits : Unqual; 6 7 @safe: 8 9 /** Array type with deterministic control of memory. The memory allocated for 10 * the array is reclaimed as soon as possible; there is no reliance on the 11 * garbage collector. Array uses malloc, realloc and free for managing its own 12 * memory. 13 * 14 * Use `std.bitmanip.BitArray` for array container storing boolean values. 15 * 16 * TODO optimize by making members templates. 0.579s before, eval-dwim: 0.67s 17 * 18 * TODO Add OutputRange.writer support as 19 * https://github.com/burner/StringBuffer/blob/master/source/stringbuffer.d#L45 20 * 21 * TODO Use `std.traits.areCopyCompatibleArrays` 22 * 23 * See_Also: https://github.com/facebook/folly/blob/master/folly/docs/FBVector.md 24 */ 25 struct DynamicArray(T, 26 alias Allocator = null, // null means means to qcmeman functions. TODO use `PureMallocator` by default 27 CapacityType = size_t) // see also https://github.com/izabera/s 28 if (!is(Unqual!T == bool) && // use `BitArray` instead 29 (is(CapacityType == ulong) || // 3 64-bit words 30 is(CapacityType == uint))) // 2 64-bit words 31 { 32 @safe: 33 34 // import core.exception : onOutOfMemoryError; 35 import core.internal.traits : hasElaborateDestructor; 36 37 import std.range.primitives : isInputRange, ElementType, hasLength, hasSlicing, isInfinite; 38 import std.traits : hasIndirections, hasAliasing, 39 isMutable, TemplateOf, isArray, isAssignable, isType, hasFunctionAttributes, isIterable; 40 import core.lifetime : emplace, move, moveEmplace; 41 42 import nxt.qcmeman : malloc, calloc, realloc, free, gc_addRange, gc_removeRange; 43 import nxt.container_traits : mustAddGCRange, needsMove, isAddress; 44 45 /// Mutable element type. 46 private alias MutableE = Unqual!T; 47 48 /// Is `true` if `U` can be assign to the element type `T` of `this`. 49 enum isElementAssignable(U) = isAssignable!(MutableE, U); 50 51 pragma(inline): 52 53 /// Returns: an array of length `initialLength` with all elements default-initialized to `ElementType.init`. 54 static typeof(this) withLength()(size_t initialLength) // template-lazy 55 { 56 pragma(inline, true); 57 return withCapacityLengthZero(initialLength, initialLength, true); 58 } 59 60 /// Returns: an array with initial capacity `initialCapacity`. 61 static typeof(this) withCapacity()(size_t initialCapacity) // template-lazy 62 { 63 pragma(inline, true); 64 return withCapacityLengthZero(initialCapacity, 0, false); 65 } 66 67 static if (__traits(isCopyable, T)) 68 { 69 /** Construct using 70 * - initial length `length`, 71 * - and value of all elements `elementValue`. 72 */ 73 static typeof(this) withLengthElementValue()(size_t length, 74 T elementValue) 75 { 76 pragma(inline, true); 77 assert(length <= CapacityType.max); 78 return typeof(return)(Store(typeof(this).allocateWithValue(length, move(elementValue)), 79 cast(CapacityType)length, 80 cast(CapacityType)length)); 81 } 82 } 83 84 /** Construct using 85 * - initial capacity `capacity`, 86 * - initial length `length`, 87 * - and zeroing-flag `zero`. 88 */ 89 private static typeof(this) withCapacityLengthZero()(size_t capacity, // template-lazy 90 size_t length, 91 bool zero) @trusted 92 { 93 version(LDC) pragma(inline, true); 94 assert(capacity >= length); 95 assert(capacity <= CapacityType.max); 96 return typeof(return)(Store(typeof(this).allocate(capacity, zero), 97 cast(CapacityType)capacity, 98 cast(CapacityType)length)); 99 } 100 101 /** Emplace `thatPtr` with elements moved from `elements`. */ 102 static ref typeof(this) emplaceWithMovedElements()(typeof(this)* thatPtr, // template-lazy 103 T[] elements) @system 104 { 105 immutable length = elements.length; 106 thatPtr._store.ptr = typeof(this).allocate(length, false); 107 thatPtr._store.capacity = cast(CapacityType)length; 108 thatPtr._store.length = cast(CapacityType)length; 109 foreach (immutable i, ref e; elements[]) 110 { 111 moveEmplace(e, thatPtr._mptr[i]); 112 } 113 return *thatPtr; 114 } 115 116 /** Emplace `thatPtr` with elements copied from `elements`. */ 117 static ref typeof(this) emplaceWithCopiedElements()(typeof(this)* thatPtr, // template-lazy 118 const(T)[] elements) @system 119 if (__traits(isCopyable, T)) 120 { 121 immutable length = elements.length; 122 thatPtr._store.ptr = typeof(this).allocate(length, false); 123 thatPtr._store.capacity = cast(CapacityType)length; 124 thatPtr._store.length = cast(CapacityType)length; 125 foreach (immutable i, ref e; elements[]) 126 { 127 thatPtr._mptr[i] = cast(T)e; // TODO restrict this function using a 128 // T-trait where this cast can be @trusted 129 } 130 return *thatPtr; 131 } 132 133 private this(Store store) 134 { 135 version(debugCtors) pragma(msg, __FILE_FULL_PATH__, ":", __LINE__, ": info: ", typeof(store)); 136 _store = store; 137 } 138 139 /// Construct from uncopyable element `value`. 140 this()(T value) @trusted // template-lazy 141 if (!__traits(isCopyable, T)) 142 { 143 version(debugCtors) pragma(msg, __FILE_FULL_PATH__, ":", __LINE__, ": info: ", typeof(value)); 144 _store.ptr = typeof(this).allocate(1, false); 145 _store.capacity = 1; 146 _store.length = 1; 147 moveEmplace(value, _mptr[0]); // TODO remove `moveEmplace` when compiler does it for us 148 } 149 150 /// Construct from copyable element `value`. 151 this(U)(U value) @trusted 152 if (__traits(isCopyable, U) && 153 isElementAssignable!U) 154 { 155 version(debugCtors) pragma(msg, __FILE_FULL_PATH__, ":", __LINE__, ": info: ", typeof(value)); 156 _store.ptr = typeof(this).allocate(1, false); 157 _store.capacity = 1; 158 _store.length = 1; 159 emplace(&_mptr[0], value); 160 } 161 162 static if (__traits(isCopyable, T) && 163 !is(T == union)) // forbid copying of unions such as `HybridBin` in hashmap.d 164 { 165 static typeof(this) withElements()(const T[] elements) @trusted // template-lazy 166 { 167 version(debugCtors) pragma(msg, __FILE_FULL_PATH__, ":", __LINE__, ": info: ", typeof(elements)); 168 immutable length = elements.length; 169 auto ptr = typeof(this).allocate(length, false); 170 171 foreach (immutable i, const element; elements[]) 172 { 173 // TODO: be more strict 174 // static if (hasIndirections!T) 175 // { 176 // ptr[i] = element; 177 // } 178 // else 179 // { 180 // ptr[i] = *cast(MutableE*)&element; 181 // } 182 ptr[i] = *cast(MutableE*)&element; 183 } 184 185 // ptr[0 .. length] = elements[]; 186 return typeof(return)(Store(ptr, 187 cast(CapacityType)length, 188 cast(CapacityType)length)); 189 } 190 191 /// Returns: shallow duplicate of `this`. 192 @property DynamicArray!(Unqual!T, Allocator, CapacityType) dup()() const @trusted // template-lazy 193 { 194 pragma(inline, true); 195 return typeof(this).withElements(this[]); 196 } 197 } 198 199 /// Construct from the element(s) of the dynamic array `values`. 200 this(U)(U[] values) @trusted 201 if (isElementAssignable!(U)) 202 { 203 version(debugCtors) pragma(msg, __FILE_FULL_PATH__, ":", __LINE__, ": info: ", typeof(values)); 204 // TODO use import emplace_all instead 205 _store.ptr = allocate(values.length, false); 206 _store.capacity = values.length; 207 foreach (index; 0 .. values.length) 208 { 209 static if (needsMove!(T)) 210 { 211 move(values[index], _mptr[index]); 212 } 213 else 214 { 215 _mptr[index] = values[index]; 216 } 217 } 218 setLengthChecked(values.length); 219 } 220 221 /// Construct from the `n` number of element(s) in the static array `values`. 222 this(uint n, U)(U[n] values) @trusted 223 if (isElementAssignable!(U)) 224 { 225 version(debugCtors) pragma(msg, __FILE_FULL_PATH__, ":", __LINE__, ": info: ", typeof(values)); 226 // TODO use import emplace_all instead 227 _store.ptr = allocate(values.length, false); 228 _store.capacity = values.length; 229 static foreach (index; 0 .. values.length) 230 { 231 static if (needsMove!(T)) 232 { 233 move(values[index], _mptr[index]); 234 } 235 else 236 { 237 _mptr[index] = values[index]; 238 } 239 } 240 setLengthChecked(values.length); 241 } 242 243 /// ditto 244 this(R)(scope R values) @trusted 245 if (// isRefIterable!R && 246 isElementAssignable!(ElementType!R) && 247 !isArray!R) 248 { 249 version(debugCtors) pragma(msg, __FILE_FULL_PATH__, ":", __LINE__, ": info: ", typeof(values)); 250 static if (hasLength!R) 251 { 252 reserve(values.length); 253 size_t index = 0; 254 foreach (ref value; values) 255 { 256 _mptr[index++] = value; 257 } 258 setLengthChecked(values.length); 259 } 260 else 261 { 262 foreach (ref value; values) 263 { 264 insertBack1(value); 265 } 266 } 267 } 268 269 /** Is `true` iff the iterable container `C` can be insert to `this`. 270 */ 271 private enum isInsertableContainer(C) = (is(C == struct) && // exclude class ranges for aliasing control 272 isRefIterable!C && // elements may be non-copyable 273 !isInfinite!C && 274 isElementAssignable!(ElementType!C)); 275 276 /// Construct from the elements `values`. 277 static typeof(this) withElementsOfRange_untested(R)(R values) @trusted 278 if (isInsertableContainer!R) 279 { 280 typeof(this) result; 281 282 static if (hasLength!R) 283 { 284 result.reserve(values.length); 285 } 286 287 static if (hasLength!R && 288 hasSlicing!R && 289 __traits(isCopyable, ElementType!R) && 290 !hasElaborateDestructor!(ElementType!R)) 291 { 292 import std.algorithm.mutation : copy; 293 copy(values[0 .. values.length], 294 result._mptr[0 .. values.length]); // TODO better to use foreach instead? 295 result.setLengthChecked(values.length); 296 } 297 else 298 { 299 static if (hasLength!R) 300 { 301 size_t i = 0; 302 foreach (ref value; move(values)) // TODO remove `move` when compiler does it for us 303 { 304 static if (needsMove!(typeof(value))) 305 { 306 moveEmplace(value, result._mptr[i++]); 307 } 308 else 309 { 310 result._mptr[i++] = value; 311 } 312 } 313 result.setLengthChecked(values.length); 314 } 315 else 316 { 317 // import std.algorithm.mutation : moveEmplaceAll; 318 /* TODO optimize with `moveEmplaceAll` that does a raw copy and 319 * zeroing of values */ 320 foreach (ref value; move(values)) // TODO remove `move` when compiler does it for us 321 { 322 static if (needsMove!(ElementType!R)) 323 { 324 result.insertBackMove(value); // steal element 325 } 326 else 327 { 328 result.insertBack1(value); 329 } 330 } 331 } 332 } 333 return result; 334 } 335 336 /// No default copying. 337 @disable this(this); 338 339 // TODO: this gives error in insertBack. why? 340 // void opAssign()(typeof(this) rhs) @trusted pure nothrow @nogc // template-lazy 341 // { 342 // move(rhs, this); 343 // } 344 345 /** Destruct. 346 * 347 * TODO what effect does have here? 348 * See_Also: https://github.com/atilaneves/automem/blob/master/source/automem/vector.d#L92 349 */ 350 ~this() @nogc /*TODO scope*/ 351 { 352 release(); 353 } 354 355 /// Empty. 356 void clear() @nogc 357 { 358 release(); 359 resetInternalData(); 360 } 361 362 /// Release internal store. 363 private void release() @nogc 364 { 365 static if (hasElaborateDestructor!T) 366 { 367 destroyElements(); 368 } 369 freeStore(); 370 } 371 372 /// Destroy elements. 373 static if (hasElaborateDestructor!T) 374 { 375 private void destroyElements() @trusted 376 { 377 foreach (immutable index; 0 .. _store.length) 378 { 379 .destroy(_mptr[index]); 380 } 381 } 382 } 383 384 /// Free internal store. 385 private void freeStore() @trusted 386 { 387 static if (mustAddGCRange!T) 388 { 389 gc_removeRange(_mptr); 390 } 391 free(_mptr); 392 } 393 394 /// Reset internal data. 395 private void resetInternalData() @nogc 396 { 397 pragma(inline, true); 398 _store.ptr = null; 399 _store.capacity = 0; 400 _store.length = 0; 401 } 402 403 /** Allocate heap region with `initialCapacity` number of elements of type `T`. 404 * 405 * If `zero` is `true` they will be zero-initialized. 406 */ 407 private static MutableE* allocate(size_t initialCapacity, bool zero) @trusted 408 { 409 immutable size_t numBytes = initialCapacity * T.sizeof; 410 411 typeof(return) ptr = null; 412 static if (!is(typeof(Allocator) == typeof(null))) 413 { 414 if (zero) 415 { 416 import std.experimental.allocator : makeArray; 417 ptr = Allocator.makeArray!T(initialCapacity, 0).ptr; // TODO set length 418 } 419 else 420 { 421 ptr = cast(typeof(return))Allocator.allocate(numBytes).ptr; // TODo set length 422 } 423 } 424 else 425 { 426 if (zero) 427 { 428 ptr = cast(typeof(return))calloc(initialCapacity, T.sizeof); 429 } 430 else 431 { 432 ptr = cast(typeof(return))malloc(numBytes); 433 } 434 assert(ptr, "Allocation failed"); 435 } 436 437 if (ptr is null && initialCapacity >= 1 ) 438 { 439 // onOutOfMemoryError(); 440 return null; 441 } 442 443 static if (mustAddGCRange!T) 444 { 445 gc_addRange(ptr, numBytes); 446 } 447 448 return ptr; 449 } 450 451 static if (__traits(isCopyable, T)) 452 { 453 /** Allocate heap region with `initialCapacity` number of elements of type `T` all set to `elementValue`. 454 */ 455 private static MutableE* allocateWithValue(size_t initialCapacity, 456 T elementValue) @trusted 457 { 458 immutable size_t numBytes = initialCapacity * T.sizeof; 459 460 typeof(return) ptr = null; 461 static if (!is(typeof(Allocator) == typeof(null))) 462 { 463 import std.experimental.allocator : makeArray; 464 ptr = Allocator.makeArray!T(initialCapacity, elementValue).ptr; // TODO set length 465 if (ptr is null && initialCapacity >= 1) 466 { 467 // onOutOfMemoryError(); 468 return null; 469 } 470 } 471 else 472 { 473 ptr = cast(typeof(return))malloc(numBytes); 474 if (ptr is null && initialCapacity >= 1) 475 { 476 // onOutOfMemoryError(); 477 return null; 478 } 479 foreach (immutable index; 0 .. initialCapacity) 480 { 481 emplace(&ptr[index], elementValue); 482 } 483 } 484 485 static if (mustAddGCRange!T) 486 { 487 gc_addRange(ptr, numBytes); 488 } 489 490 return ptr; 491 } 492 } 493 494 /** Comparison for equality. */ 495 bool opEquals()(const scope auto ref typeof(this) rhs) const scope // template-lazy 496 { 497 pragma(inline, true); 498 return slice() == rhs.slice(); 499 } 500 /// ditto 501 bool opEquals(U)(const scope U[] rhs) const scope 502 if (is(typeof(T[].init == U[].init))) 503 { 504 pragma(inline, true); 505 return slice() == rhs; 506 } 507 508 /// Calculate D associative array (AA) key hash. 509 hash_t toHash()() const scope @trusted // template-lazy 510 { 511 import core.internal.hash : hashOf; 512 static if (__traits(isCopyable, T)) 513 { 514 return this.length ^ hashOf(slice()); 515 } 516 else 517 { 518 typeof(return) hash = this.length; 519 foreach (immutable index; 0 .. this.length) 520 { 521 hash ^= this.ptr[index].hashOf; 522 } 523 return hash; 524 } 525 } 526 527 static if (__traits(isCopyable, T)) 528 { 529 /** Construct a string representation of `this` at `sink`. 530 */ 531 void toString()(scope void delegate(scope const(char)[]) sink) const scope // template-lazy 532 { 533 sink("["); 534 foreach (immutable ix, ref value; slice()) 535 { 536 import std.conv : to; 537 sink(to!string(value)); 538 if (ix + 1 < length) { sink(", "); } // separator 539 } 540 sink("]"); 541 } 542 } 543 544 /// Check if empty. 545 @property bool empty()() const scope // template-lazy 546 { 547 pragma(inline, true); 548 return _store.length == 0; 549 } 550 551 /// Get length. 552 @property size_t length() const scope // can't be template-lazy 553 { 554 pragma(inline, true) 555 return _store.length; 556 } 557 alias opDollar = length; /// ditto 558 559 /** Set length to `newLength`. 560 * 561 * If `newLength` < `length` elements are truncate. 562 * If `newLength` > `length` default-initialized elements are appended. 563 */ 564 @property void length(size_t newLength) @trusted scope // can't template-lazy 565 { 566 if (newLength < length) // if truncatation 567 { 568 static if (hasElaborateDestructor!T) 569 { 570 foreach (immutable index; newLength .. _store.length) 571 { 572 .destroy(_mptr[index]); 573 } 574 } 575 else static if (isAddress!T) 576 { 577 foreach (immutable index; newLength .. _store.length) 578 { 579 _mptr[index] = null; // please the GC 580 } 581 } 582 } 583 else 584 { 585 reserve(newLength); 586 static if (hasElaborateDestructor!T) 587 { 588 // TODO remove when compiler does it for us 589 foreach (immutable index; _store.length .. newLength) 590 { 591 // TODO remove when compiler does it for us: 592 static if (__traits(isCopyable, T)) 593 { 594 emplace(&_mptr[index], T.init); 595 } 596 else 597 { 598 auto _ = T.init; 599 moveEmplace(_, _mptr[index]); 600 } 601 } 602 } 603 else 604 { 605 _mptr[_store.length .. newLength] = T.init; 606 } 607 } 608 609 setLengthChecked(newLength); 610 } 611 612 /// Set capacity, checking for overflow when `CapacityType` is not `size_t`. 613 private void setLengthChecked(size_t newLength) scope 614 { 615 static if (!is(CapacityType == size_t)) 616 { 617 assert(newLength <= CapacityType.max, 618 "New length doesn't fit in capacity type."); 619 } 620 _store.length = cast(CapacityType)newLength; 621 } 622 623 /// Get capacity. 624 @property size_t capacity() const scope // can't be template-lazy 625 { 626 pragma(inline, true); 627 return _store.capacity; 628 } 629 630 /** Ensures sufficient capacity to accommodate for minimumCapacity number 631 * of elements. If `minimumCapacity` < `capacity`, this method does 632 * nothing. 633 */ 634 void reserve(size_t minimumCapacity) @trusted scope pure nothrow @nogc 635 { 636 static if (!is(CapacityType == size_t)) 637 { 638 assert(minimumCapacity <= CapacityType.max, 639 "Minimum capacity doesn't fit in capacity type."); 640 } 641 642 if (minimumCapacity <= capacity) { return; } 643 644 // growth factor 645 // Motivation: https://github.com/facebook/folly/blob/master/folly/docs/FBVector.md#memory-handling 646 reallocateAndSetCapacity(3*minimumCapacity/2); // use 1.5 like Facebook's `fbvector` does 647 // import std.math : nextPow2; 648 // reallocateAndSetCapacity(minimumCapacity.nextPow2); 649 } 650 651 /// Reallocate storage. 652 private void reallocateAndSetCapacity()(size_t newCapacity) @trusted // template-lazy 653 { 654 static if (!is(CapacityType == size_t)) 655 { 656 assert(newCapacity <= CapacityType.max, 657 "New capacity doesn't fit in capacity type."); 658 } 659 660 static if (mustAddGCRange!T) 661 { 662 gc_removeRange(_store.ptr); 663 } 664 665 _store.capacity = cast(CapacityType)newCapacity; 666 _store.ptr = cast(T*)realloc(_mptr, T.sizeof * _store.capacity); 667 if (_store.ptr is null && newCapacity >= 1) 668 { 669 // onOutOfMemoryError(); 670 return; 671 } 672 673 static if (mustAddGCRange!T) 674 { 675 gc_addRange(_store.ptr, _store.capacity * T.sizeof); 676 } 677 } 678 679 /// Index support. 680 ref inout(T) opIndex()(size_t i) inout return // template-lazy 681 { 682 pragma(inline, true); 683 return slice()[i]; 684 } 685 686 /// Slice support. 687 inout(T)[] opSlice()(size_t i, size_t j) inout return // template-lazy 688 { 689 pragma(inline, true); 690 return slice()[i .. j]; 691 } 692 /// ditto 693 inout(T)[] opSlice()() inout return // template-lazy 694 { 695 pragma(inline, true); 696 return slice(); 697 } 698 699 /// Index assignment support. 700 ref T opIndexAssign(U)(scope U value, size_t i) @trusted return 701 { 702 static if (hasElaborateDestructor!T) 703 { 704 move(*(cast(MutableE*)(&value)), _mptr[i]); // TODO is this correct? 705 } 706 else static if (hasIndirections!T && // TODO `hasAliasing` instead? 707 !isMutable!T) 708 { 709 static assert("Cannot modify constant elements with indirections"); 710 } 711 else 712 { 713 slice()[i] = value; 714 } 715 return slice()[i]; 716 } 717 718 /// Slice assignment support. 719 T[] opSliceAssign(U)(scope U value) return 720 { 721 pragma(inline, true); 722 return slice()[] = value; 723 } 724 725 /// ditto 726 T[] opSliceAssign(U)(scope U value, size_t i, size_t j) return 727 { 728 pragma(inline, true); 729 return slice()[i .. j] = value; 730 } 731 732 /// Get reference to front element. 733 ref inout(T) front()() inout return @property // template-lazy 734 { 735 pragma(inline, true); 736 return slice()[0]; // range-checked by default 737 } 738 739 /// Get reference to back element. 740 ref inout(T) back()() inout return @property // template-lazy 741 { 742 pragma(inline, true); 743 return slice()[_store.length - 1]; // range-checked by default 744 745 } 746 747 /** Move `value` into the end of the array. 748 */ 749 void insertBackMove()(ref T value) @trusted // template-lazy 750 { 751 version(LDC) pragma(inline, true); 752 reserve(_store.length + 1); 753 moveEmplace(value, _mptr[_store.length]); 754 _store.length += 1; 755 } 756 757 /** Insert unmoveable `value` into the end of the array. 758 */ 759 void insertBack()(T value) @trusted // template-lazy 760 if (!__traits(isCopyable, T)) 761 { 762 version(LDC) pragma(inline, true); 763 insertBackMove(value); 764 } 765 766 /** Insert the elements `values` into the end of the array. 767 */ 768 void insertBack(U)(U[] values...) @trusted 769 if (isElementAssignable!U && 770 __traits(isCopyable, U)) // prevent accidental move of l-value `values` 771 { 772 if (values.length == 1) // TODO branch should be detected at compile-time 773 { 774 // twice as fast as array assignment below 775 return insertBack1(values[0]); 776 } 777 static if (is(T == immutable(T))) 778 { 779 /* An array of immutable values cannot overlap with the `this` 780 mutable array container data, which entails no need to check for 781 overlap. 782 */ 783 reserve(_store.length + values.length); 784 _mptr[_store.length .. _store.length + values.length] = values; 785 } 786 else 787 { 788 import nxt.overlapping : overlaps; 789 if (_store.ptr == values.ptr) // called for instances as: `this ~= this` 790 { 791 reserve(2*_store.length); // invalidates `values.ptr` 792 foreach (immutable i; 0 .. _store.length) 793 { 794 _mptr[_store.length + i] = _store.ptr[i]; 795 } 796 } 797 else if (overlaps(this[], values[])) 798 { 799 assert(0, `TODO Handle overlapping arrays`); 800 } 801 else 802 { 803 reserve(_store.length + values.length); 804 _mptr[_store.length .. _store.length + values.length] = values; 805 } 806 } 807 _store.length += values.length; 808 } 809 810 /** Insert `value` into the end of the array. 811 * 812 * TODO rename to `insertBack` and make this steal scalar calls over 813 * insertBack(U)(U[] values...) overload below 814 */ 815 void insertBack1()(T value) @trusted // template-lazy 816 { 817 reserve(_store.length + 1); 818 static if (needsMove!T) 819 { 820 insertBackMove(*cast(MutableE*)(&value)); 821 } 822 else 823 { 824 _mptr[_store.length] = value; 825 } 826 _store.length += 1; 827 } 828 829 /** Insert the elements `elements` into the end of the array. 830 */ 831 void insertBack(R)(scope R elements) @trusted 832 if (isInsertableContainer!R) 833 { 834 import std.range.primitives : hasLength; 835 static if (isInputRange!R && 836 hasLength!R) 837 { 838 reserve(_store.length + elements.length); 839 import std.algorithm.mutation : copy; 840 copy(elements, _mptr[_store.length .. _store.length + elements.length]); 841 _store.length += elements.length; 842 } 843 else 844 { 845 foreach (ref element; move(elements)) // TODO remove `move` when compiler does it for us 846 { 847 static if (__traits(isCopyable, ElementType!R)) 848 { 849 insertBack(element); 850 } 851 else 852 { 853 insertBackMove(element); 854 } 855 } 856 } 857 } 858 859 /// ditto 860 alias put = insertBack; 861 862 /** Remove last value fromm the end of the array. 863 */ 864 void popBack()() @trusted // template-lazy 865 { 866 pragma(inline, true); 867 assert(!empty); 868 _store.length -= 1; 869 static if (hasElaborateDestructor!T) 870 { 871 .destroy(_mptr[_store.length]); 872 } 873 else static if (isAddress!T) 874 { 875 _mptr[_store.length] = null; // please the GC 876 } 877 } 878 879 /** Rmove `n` last values from the end of the array. 880 * 881 * See_Also: http://mir-algorithm.libmir.org/mir_appender.html#.ScopedBuffer.popBackN 882 */ 883 void popBackN()(size_t n) @trusted // template-lazy 884 { 885 assert(length >= n); 886 _store.length -= n; 887 static if (hasElaborateDestructor!T) 888 { 889 foreach (const index; 0 .. n) 890 { 891 .destroy(_mptr[_store.length + index]); 892 } 893 } 894 else static if (isAddress!T) 895 { 896 foreach (const index; 0 .. n) 897 { 898 _mptr[_store.length + index] = null; // please the GC 899 } 900 } 901 } 902 903 /** Pop back element and return it. 904 * 905 * This is well-missed feature of C++'s `std::vector` because of problems 906 * with exception handling. For more details see 907 * https://stackoverflow.com/questions/12600330/pop-back-return-value. 908 */ 909 T backPop()() @trusted // template-lazy 910 { 911 pragma(inline, true); 912 assert(!empty); 913 _store.length -= 1; 914 static if (needsMove!T) 915 { 916 return move(_mptr[_store.length]); // move is indeed need here 917 } 918 else 919 { 920 return _mptr[_store.length]; // no move needed 921 } 922 } 923 924 /** Pop element at `index`. */ 925 void popAt()(size_t index) @trusted // template-lazy 926 @("complexity", "O(length)") 927 { 928 assert(index < this.length); 929 static if (hasElaborateDestructor!T) 930 { 931 .destroy(_mptr[index]); 932 } 933 else static if (isAddress!T) 934 { 935 _mptr[index] = null; // please the GC 936 } 937 shiftToFrontAt(index); 938 _store.length -= 1; 939 } 940 941 /** Move element at `index` to return. */ 942 T moveAt()(size_t index) @trusted // template-lazy 943 @("complexity", "O(length)") 944 { 945 assert(index < this.length); 946 auto value = move(_mptr[index]); 947 shiftToFrontAt(index); 948 _store.length -= 1; 949 return move(value); // TODO remove `move` when compiler does it for us 950 } 951 952 /** Move element at front. */ 953 T frontPop()() // template-lazy 954 @("complexity", "O(length)") 955 { 956 pragma(inline, true); 957 return moveAt(0); 958 } 959 960 private void shiftToFrontAt()(size_t index) @trusted // template-lazy 961 { 962 // TODO use this instead: 963 // immutable si = index + 1; // source index 964 // immutable ti = index; // target index 965 // immutable restLength = this.length - (index + 1); 966 // import std.algorithm.mutation : moveEmplaceAll; 967 // moveEmplaceAll(_mptr[si .. si + restLength], 968 // _mptr[ti .. ti + restLength]); 969 foreach (immutable i; 0 .. this.length - (index + 1)) // each element index that needs to be moved 970 { 971 immutable si = index + i + 1; // source index 972 immutable ti = index + i; // target index 973 moveEmplace(_mptr[si], // TODO remove `move` when compiler does it for us 974 _mptr[ti]); 975 } 976 } 977 978 /** Forwards to $(D insertBack(values)). 979 */ 980 void opOpAssign(string op)(T value) 981 if (op == "~") 982 { 983 pragma(inline, true); 984 insertBackMove(value); 985 } 986 987 /// ditto 988 void opOpAssign(string op, U)(U[] values...) @trusted 989 if (op == "~" && 990 isElementAssignable!U && 991 __traits(isCopyable, U)) // prevent accidental move of l-value `values` 992 { 993 pragma(inline, true); 994 insertBack(values); 995 } 996 997 /// ditto 998 void opOpAssign(string op, R)(R values) 999 if (op == "~" && 1000 isInputRange!R && 1001 !isInfinite!R && 1002 !isArray!R && 1003 isElementAssignable!(ElementType!R)) 1004 { 1005 pragma(inline, true); 1006 insertBack(values); 1007 } 1008 1009 void opOpAssign(string op)(auto ref typeof(this) values) 1010 if (op == "~") 1011 { 1012 pragma(inline, true); 1013 insertBack(values[]); 1014 } 1015 1016 // typeof(this) opBinary(string op, R)(R values) 1017 // if (op == "~") 1018 // { 1019 // // TODO: optimize 1020 // typeof(this) result; 1021 // result ~= this[]; 1022 // assert(result.length == length); 1023 // result ~= values[]; 1024 // return result; 1025 // } 1026 1027 /// Helper slice. 1028 private inout(T)[] slice() inout return @trusted 1029 { 1030 pragma(inline, true); 1031 return _store.ptr[0 .. _store.length]; 1032 } 1033 1034 /// Unsafe access to pointer. 1035 inout(T)* ptr()() inout return @system // template-lazy 1036 { 1037 pragma(inline, true); 1038 return _store.ptr; 1039 } 1040 1041 /// Mutable pointer. 1042 private MutableE* _mptr() const return @trusted 1043 { 1044 pragma(inline, true); 1045 return cast(typeof(return))_store.ptr; 1046 } 1047 1048 private: 1049 /** For more convenient construction. */ 1050 struct Store 1051 { 1052 static if (!is(typeof(Allocator) == typeof(null)) && 1053 !hasFunctionAttributes!(Allocator.allocate, "@nogc")) 1054 { 1055 T* ptr; // GC-allocated store pointer 1056 } 1057 else 1058 { 1059 import nxt.gc_traits : NoGc; 1060 @NoGc T* ptr; // non-GC-allocated store pointer 1061 } 1062 1063 CapacityType capacity; // store capacity 1064 CapacityType length; // store length 1065 } 1066 1067 Store _store; 1068 } 1069 1070 import std.traits : isInstanceOf; 1071 import std.functional : unaryFun; 1072 1073 /** Remove all elements matching `predicate`. 1074 * 1075 * Returns: number of elements that were removed. 1076 * 1077 * TODO implement version that doesn't use a temporary array `tmp`, which is 1078 * probably faster for small arrays. 1079 */ 1080 size_t remove(alias predicate, C)(ref C c) @trusted 1081 @("complexity", "O(length)") 1082 if (isInstanceOf!(DynamicArray, C) && 1083 is(typeof(unaryFun!predicate(C.init[0])))) 1084 { 1085 C tmp; 1086 size_t count = 0; 1087 foreach (immutable i; 0 .. c.length) 1088 { 1089 if (unaryFun!predicate(c[i])) 1090 { 1091 count += 1; 1092 import core.internal.traits : hasElaborateDestructor; 1093 import nxt.container_traits : isAddress; 1094 static if (hasElaborateDestructor!(typeof(c[i]))) 1095 { 1096 .destroy(c[i]); 1097 } 1098 else static if (isAddress!(typeof(c[i]))) 1099 { 1100 c[i] = null; // please the GC 1101 } 1102 } 1103 else 1104 { 1105 tmp.insertBackMove(c[i]); // TODO remove unnecessary clearing of `_mptr[i]` 1106 } 1107 } 1108 1109 c.freeStore(); 1110 1111 import core.lifetime : moveEmplace; 1112 moveEmplace(tmp, c); 1113 1114 return count; 1115 } 1116 1117 /// construct and append from slices 1118 @safe pure nothrow @nogc unittest 1119 { 1120 alias T = int; 1121 alias A = DynamicArray!(T, null, uint); 1122 static if (size_t.sizeof == 8) // only 64-bit 1123 { 1124 static assert(A.sizeof == 2 * size_t.sizeof); // only two words 1125 } 1126 1127 auto a = A([10, 11, 12].s); 1128 1129 a ~= a[]; 1130 assert(a[] == [10, 11, 12, 1131 10, 11, 12].s); 1132 1133 a ~= false; 1134 assert(a[] == [10, 11, 12, 1135 10, 11, 12, 0].s); 1136 } 1137 1138 /// 1139 @safe pure nothrow @nogc unittest 1140 { 1141 alias T = int; 1142 alias A = DynamicArray!(T); 1143 1144 A a; 1145 1146 a.length = 1; 1147 assert(a.length == 1); 1148 assert(a.capacity >= 1); 1149 1150 a[0] = 10; 1151 1152 a.insertBack(11, 12); 1153 1154 a ~= T.init; 1155 a.insertBack([3].s); 1156 assert(a[] == [10, 11, 12, 0, 3].s); 1157 1158 import std.algorithm.iteration : filter; 1159 1160 a.insertBack([42].s[].filter!(_ => _ is 42)); 1161 assert(a[] == [10, 11, 12, 0, 3, 42].s); 1162 1163 a.insertBack([42].s[].filter!(_ => _ !is 42)); 1164 assert(a[] == [10, 11, 12, 0, 3, 42].s); 1165 1166 a ~= a[]; 1167 assert(a[] == [10, 11, 12, 0, 3, 42, 1168 10, 11, 12, 0, 3, 42].s); 1169 } 1170 1171 /// 1172 @safe pure nothrow @nogc unittest 1173 { 1174 alias T = int; 1175 alias A = DynamicArray!(T); 1176 1177 A a; // default construction allowed 1178 assert(a.empty); 1179 assert(a.length == 0); 1180 assert(a.capacity == 0); 1181 assert(a[] == []); 1182 1183 auto b = DynamicArray!int.withLength(3); 1184 assert(!b.empty); 1185 assert(b.length == 3); 1186 assert(b.capacity == 3); 1187 b[0] = 1; 1188 b[1] = 2; 1189 b[2] = 3; 1190 assert(b[] == [1, 2, 3].s); 1191 1192 b[] = [4, 5, 6].s; 1193 assert(b[] == [4, 5, 6].s); 1194 1195 const c = DynamicArray!int.withCapacity(3); 1196 assert(c.empty); 1197 assert(c.capacity == 3); 1198 assert(c[] == []); 1199 1200 // TODO this should fail with -dip1000 1201 auto f() @safe 1202 { 1203 A a; 1204 return a[]; 1205 } 1206 auto d = f(); 1207 1208 const e = DynamicArray!int([1, 2, 3, 4].s); 1209 assert(e.length == 4); 1210 assert(e[] == [1, 2, 3, 4].s); 1211 } 1212 1213 /// 1214 @trusted pure nothrow @nogc unittest 1215 { 1216 alias T = int; 1217 alias A = DynamicArray!(T); 1218 1219 auto a = A([1, 2, 3].s); 1220 A b = a.dup; // copy construction enabled 1221 1222 assert(a[] == b[]); // same content 1223 assert(&a[0] !is &b[0]); // but not the same 1224 1225 assert(b[] == [1, 2, 3].s); 1226 assert(b.length == 3); 1227 1228 b ~= 4; 1229 assert(a != b); 1230 a.clear(); 1231 assert(a != b); 1232 b.clear(); 1233 assert(a == b); 1234 1235 auto c = A([1, 2, 3].s); 1236 } 1237 1238 /// DIP-1000 return ref escape analysis 1239 @safe pure nothrow @nogc unittest 1240 { 1241 alias T = int; 1242 alias A = DynamicArray!T; 1243 1244 T[] leakSlice() @safe pure nothrow @nogc 1245 { 1246 A a; 1247 return a[]; // TODO shouldn't compile with -dip1000 1248 } 1249 1250 T* leakPointer() @safe pure nothrow @nogc 1251 { 1252 A a; 1253 return a._store.ptr; // TODO shouldn't compile with -dip1000 1254 } 1255 1256 auto lp = leakPointer(); // TODO shouldn't compile with -dip1000 1257 auto ls = leakSlice(); // TODO shouldn't compile with -dip1000 1258 } 1259 1260 version(unittest) 1261 { 1262 private static struct SomeUncopyable 1263 { 1264 @disable this(this); 1265 int x; 1266 } 1267 } 1268 1269 /// construct and insert from non-copyable element type passed by value 1270 @safe pure nothrow /*@nogc*/ unittest 1271 { 1272 alias A = DynamicArray!(SomeUncopyable); 1273 1274 A a = A(SomeUncopyable(17)); 1275 assert(a[] == [SomeUncopyable(17)]); 1276 1277 a.insertBack(SomeUncopyable(18)); 1278 assert(a[] == [SomeUncopyable(17), 1279 SomeUncopyable(18)]); 1280 1281 a ~= SomeUncopyable(19); 1282 assert(a[] == [SomeUncopyable(17), 1283 SomeUncopyable(18), 1284 SomeUncopyable(19)]); 1285 } 1286 1287 /// construct from slice of uncopyable type 1288 @safe pure nothrow @nogc unittest 1289 { 1290 alias A = DynamicArray!(SomeUncopyable); 1291 // TODO can we safely support this?: A a = [SomeUncopyable(17)]; 1292 } 1293 1294 // construct from array with uncopyable elements 1295 @safe pure nothrow @nogc unittest 1296 { 1297 alias A = DynamicArray!(SomeUncopyable); 1298 1299 A a; 1300 assert(a.empty); 1301 1302 a.insertBack(A.init); 1303 assert(a.empty); 1304 } 1305 1306 // construct from ranges of uncopyable elements 1307 @safe pure nothrow @nogc unittest 1308 { 1309 alias T = SomeUncopyable; 1310 alias A = DynamicArray!T; 1311 1312 A a; 1313 assert(a.empty); 1314 1315 import std.algorithm.iteration : map, filter; 1316 1317 const b = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => T(_^^2))); // hasLength 1318 assert(b.length == 3); 1319 assert(b == [T(100), T(400), T(900)].s); 1320 1321 const c = A.withElementsOfRange_untested([10, 20, 30].s[].filter!(_ => _ == 30).map!(_ => T(_^^2))); // !hasLength 1322 assert(c.length == 1); 1323 assert(c[0].x == 900); 1324 } 1325 1326 // construct from ranges of copyable elements 1327 @safe pure nothrow @nogc unittest 1328 { 1329 alias T = int; 1330 alias A = DynamicArray!T; 1331 1332 A a; 1333 assert(a.empty); 1334 1335 import std.algorithm.iteration : map, filter; 1336 1337 const b = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => T(_^^2))); // hasLength 1338 assert(b.length == 3); 1339 assert(b == [T(100), T(400), T(900)].s); 1340 1341 const c = A.withElementsOfRange_untested([10, 20, 30].s[].filter!(_ => _ == 30).map!(_ => T(_^^2))); // !hasLength 1342 assert(c == [T(900)].s); 1343 } 1344 1345 /// construct with string as element type that needs GC-range 1346 @safe pure nothrow @nogc unittest 1347 { 1348 alias T = string; 1349 alias A = DynamicArray!(T); 1350 1351 A a; 1352 a ~= `alpha`; 1353 a ~= `beta`; 1354 a ~= [`gamma`, `delta`].s; 1355 assert(a[] == [`alpha`, `beta`, `gamma`, `delta`].s); 1356 1357 const b = [`epsilon`].s; 1358 1359 a.insertBack(b); 1360 assert(a[] == [`alpha`, `beta`, `gamma`, `delta`, `epsilon`].s); 1361 1362 a ~= b; 1363 assert(a[] == [`alpha`, `beta`, `gamma`, `delta`, `epsilon`, `epsilon`].s); 1364 } 1365 1366 /// convert to string 1367 unittest 1368 { 1369 alias T = int; 1370 alias A = DynamicArray!(T); 1371 1372 DynamicArray!char sink; 1373 // TODO make this work: A([1, 2, 3]).toString(sink.put); 1374 } 1375 1376 /// iteration over mutable elements 1377 @safe pure nothrow @nogc unittest 1378 { 1379 alias T = int; 1380 alias A = DynamicArray!(T); 1381 1382 auto a = A([1, 2, 3].s); 1383 1384 foreach (immutable i, const e; a) 1385 { 1386 assert(i + 1 == e); 1387 } 1388 } 1389 1390 /// iteration over `const`ant elements 1391 @safe pure nothrow @nogc unittest 1392 { 1393 alias T = const(int); 1394 alias A = DynamicArray!(T); 1395 1396 auto a = A([1, 2, 3].s); 1397 1398 foreach (immutable i, const e; a) 1399 { 1400 assert(i + 1 == e); 1401 } 1402 } 1403 1404 /// iteration over immutable elements 1405 @safe pure nothrow @nogc unittest 1406 { 1407 alias T = immutable(int); 1408 alias A = DynamicArray!(T); 1409 1410 auto a = A([1, 2, 3].s); 1411 1412 foreach (immutable i, const e; a) 1413 { 1414 assert(i + 1 == e); 1415 } 1416 } 1417 1418 /// removal 1419 @safe pure nothrow @nogc unittest 1420 { 1421 alias T = int; 1422 alias A = DynamicArray!(T); 1423 1424 auto a = A([1, 2, 3].s); 1425 assert(a == [1, 2, 3].s); 1426 1427 assert(a.frontPop() == 1); 1428 assert(a == [2, 3].s); 1429 1430 a.popAt(1); 1431 assert(a == [2].s); 1432 1433 a.popAt(0); 1434 assert(a == []); 1435 1436 a.insertBack(11); 1437 assert(a == [11].s); 1438 1439 assert(a.backPop == 11); 1440 1441 a.insertBack(17); 1442 assert(a == [17].s); 1443 a.popBack(); 1444 assert(a.empty); 1445 1446 a.insertBack([11, 12, 13, 14, 15].s[]); 1447 a.popAt(2); 1448 assert(a == [11, 12, 14, 15].s); 1449 a.popAt(0); 1450 assert(a == [12, 14, 15].s); 1451 a.popAt(2); 1452 1453 assert(a == [12, 14].s); 1454 1455 a ~= a; 1456 } 1457 1458 /// removal 1459 @safe pure nothrow unittest 1460 { 1461 import nxt.container_traits : mustAddGCRange; 1462 1463 size_t mallocCount = 0; 1464 size_t freeCount = 0; 1465 1466 struct S 1467 { 1468 @safe pure nothrow @nogc: 1469 1470 alias E = int; 1471 1472 import nxt.qcmeman : malloc, free; 1473 1474 this(E x) @trusted 1475 { 1476 _ptr = cast(E*)malloc(E.sizeof); 1477 mallocCount += 1; 1478 *_ptr = x; 1479 } 1480 1481 @disable this(this); 1482 1483 ~this() @trusted @nogc 1484 { 1485 free(_ptr); 1486 freeCount += 1; 1487 } 1488 1489 import nxt.gc_traits : NoGc; 1490 @NoGc E* _ptr; 1491 } 1492 1493 /* D compilers cannot currently move stuff efficiently when using 1494 * std.algorithm.mutation.move. A final dtor call to the cleared sourced is 1495 * always done. */ 1496 size_t extraDtor = 1; 1497 1498 alias A = DynamicArray!(S); 1499 static assert(!mustAddGCRange!A); 1500 alias AA = DynamicArray!(A); 1501 static assert(!mustAddGCRange!AA); 1502 1503 assert(mallocCount == 0); 1504 1505 { 1506 A a; 1507 a.insertBack(S(11)); 1508 assert(mallocCount == 1); 1509 assert(freeCount == extraDtor + 0); 1510 } 1511 1512 assert(freeCount == extraDtor + 1); 1513 1514 // assert(a.front !is S(11)); 1515 // assert(a.back !is S(11)); 1516 // a.insertBack(S(12)); 1517 } 1518 1519 /// test `OutputRange` behaviour with std.format 1520 version(none) // TODO replace with other exercise of std.format 1521 @safe pure /*TODO nothrow @nogc*/ unittest 1522 { 1523 import std.format : formattedWrite; 1524 const x = "42"; 1525 alias A = DynamicArray!(char); 1526 A a; 1527 a.formattedWrite!("x : %s")(x); 1528 assert(a == "x : 42"); 1529 } 1530 1531 /// test emplaceWithMovedElements 1532 @trusted pure nothrow @nogc unittest 1533 { 1534 const x = "42"; 1535 alias A = DynamicArray!(char); 1536 1537 auto ae = ['a', 'b'].s; 1538 1539 A a = void; 1540 A.emplaceWithMovedElements(&a, ae[]); 1541 1542 assert(a.length == ae.length); 1543 assert(a.capacity == ae.length); 1544 assert(a[] == ae); 1545 } 1546 1547 /// test emplaceWithCopiedElements 1548 @trusted pure nothrow @nogc unittest 1549 { 1550 const x = "42"; 1551 alias A = DynamicArray!(char); 1552 1553 auto ae = ['a', 'b'].s; 1554 1555 A a = void; 1556 A.emplaceWithCopiedElements(&a, ae[]); 1557 1558 assert(a.length == ae.length); 1559 assert(a.capacity == ae.length); 1560 assert(a[] == ae); 1561 } 1562 1563 @safe pure nothrow @nogc unittest 1564 { 1565 alias T = int; 1566 alias A = DynamicArray!(T, null, uint); 1567 const a = A(17); 1568 assert(a[] == [17].s); 1569 } 1570 1571 /// check duplication via `dup` 1572 @trusted pure nothrow @nogc unittest 1573 { 1574 alias T = int; 1575 alias A = DynamicArray!(T); 1576 1577 static assert(!__traits(compiles, { A b = a; })); // copying disabled 1578 1579 auto a = A([10, 11, 12].s); 1580 auto b = a.dup; 1581 assert(a == b); 1582 assert(&a[0] !is &b[0]); 1583 } 1584 1585 /// element type is a class 1586 @safe pure nothrow unittest 1587 { 1588 class T 1589 { 1590 this (int x) 1591 { 1592 this.x = x; 1593 } 1594 ~this() @nogc { x = 42; } 1595 int x; 1596 } 1597 alias A = DynamicArray!(T); 1598 auto a = A([new T(10), 1599 new T(11), 1600 new T(12)].s); 1601 assert(a.length == 3); 1602 a.remove!(_ => _.x == 12); 1603 assert(a.length == 2); 1604 } 1605 1606 /// check filtered removal via `remove` 1607 @safe pure nothrow @nogc unittest 1608 { 1609 struct T 1610 { 1611 int value; 1612 } 1613 1614 alias A = DynamicArray!(T); 1615 1616 static assert(!__traits(compiles, { A b = a; })); // copying disabled 1617 1618 auto a = A([T(10), T(11), T(12)].s); 1619 1620 assert(a.remove!"a.value == 13" == 0); 1621 assert(a[] == [T(10), T(11), T(12)].s); 1622 1623 assert(a.remove!"a.value >= 12" == 1); 1624 assert(a[] == [T(10), T(11)].s); 1625 1626 assert(a.remove!(_ => _.value == 10) == 1); 1627 assert(a[] == [T(11)].s); 1628 1629 assert(a.remove!(_ => _.value == 11) == 1); 1630 assert(a.empty); 1631 } 1632 1633 /// construct from map range 1634 @safe pure nothrow unittest 1635 { 1636 import std.algorithm.iteration : map; 1637 alias T = int; 1638 alias A = DynamicArray!(T); 1639 1640 A a = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => _^^2)); 1641 assert(a[] == [100, 400, 900].s); 1642 a.popBackN(2); 1643 assert(a.length == 1); 1644 a.popBackN(1); 1645 assert(a.empty); 1646 1647 A b = A([10, 20, 30].s[].map!(_ => _^^2)); 1648 assert(b[] == [100, 400, 900].s); 1649 b.popBackN(2); 1650 assert(b.length == 1); 1651 b.popBackN(1); 1652 assert(b.empty); 1653 1654 A c = A([10, 20, 30].s[]); 1655 assert(c[] == [10, 20, 30].s); 1656 } 1657 1658 /// construct from map range 1659 @trusted pure nothrow unittest 1660 { 1661 alias T = int; 1662 alias A = DynamicArray!(T); 1663 1664 import std.typecons : RefCounted; 1665 RefCounted!A x; 1666 1667 auto z = [1, 2, 3].s; 1668 x ~= z[]; 1669 1670 auto y = x; 1671 assert(y[] == z); 1672 1673 auto _ = x.toHash; 1674 } 1675 1676 /// construct from static array 1677 @trusted pure nothrow @nogc unittest 1678 { 1679 alias T = uint; 1680 alias A = DynamicArray!(T); 1681 1682 ushort[3] a = [1, 2, 3]; 1683 1684 auto x = A(a); 1685 assert(x == a); 1686 assert(x == a[]); 1687 } 1688 1689 /// construct from static array slice 1690 @trusted pure nothrow @nogc unittest 1691 { 1692 alias T = uint; 1693 alias A = DynamicArray!(T); 1694 1695 ushort[3] a = [1, 2, 3]; 1696 ushort[] b = a[]; 1697 1698 auto y = A(b); // cannot construct directly from `a[]` because its type is `ushort[3]` 1699 assert(y == a); 1700 assert(y == a[]); 1701 } 1702 1703 /// GCAllocator 1704 @trusted pure nothrow unittest 1705 { 1706 import std.experimental.allocator.gc_allocator : GCAllocator; 1707 alias T = int; 1708 alias A = DynamicArray!(T, GCAllocator.instance); 1709 A a; 1710 } 1711 1712 /// construct with slices as element types 1713 @trusted pure nothrow unittest 1714 { 1715 alias A = DynamicArray!(string); 1716 alias B = DynamicArray!(char[]); 1717 } 1718 1719 /** Variant of `DynamicArray` with copy construction (postblit) enabled. 1720 * 1721 * See_Also: suppressing.d 1722 * See_Also: http://forum.dlang.org/post/eitlbtfbavdphbvplnrk@forum.dlang.org 1723 */ 1724 struct BasicCopyableArray 1725 { 1726 /** TODO implement using instructions at: 1727 * http://forum.dlang.org/post/eitlbtfbavdphbvplnrk@forum.dlang.org 1728 */ 1729 } 1730 1731 /// TODO Move to Phobos. 1732 private enum bool isRefIterable(T) = is(typeof({ foreach (ref elem; T.init) {} })); 1733 1734 version(unittest) 1735 { 1736 import nxt.array_help : s; 1737 }