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