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