1 module nxt.container.dynamic_array; 2 3 import std.experimental.allocator.gc_allocator : GCAllocator; 4 import std.experimental.allocator.common : isAllocator; 5 6 /** Dynamic array container. 7 8 TODO: Move members dealing with `Allocator`, such as DynamicArrray.reserve, 9 and others to private members of `ArrayStore` and then rerun container 10 benchmark for `DynamicArray`. Also include a benchmark of calls to 11 `ArrayStore.reserve()`. 12 13 TODO: Generalize to bucket array either via specialized allocator to by 14 extra Storage class given as template type parameter. Integrate 15 `nxt.bucket_array` for details. 16 17 TODO: Add OutputRange.writer support as 18 https://github.com/burner/StringBuffer/blob/master/source/stringbuffer.d#L45 19 20 TODO: Use `std.traits.areCopyCompatibleArrays` 21 22 TODO: Check if using the std::vector-compatible store is faster: struct 23 Store { T* begin; T* endData; T* endCapacity; } 24 25 See: http://forum.dlang.org/thread/wswbtzakdvpgaebuhbom@forum.dlang.org See 26 also https://github.com/izabera/s */ 27 @safe struct DynamicArray(T, Allocator = GCAllocator, Capacity = size_t) 28 if (!is(immutable T == immutable bool) && // use `BitArray` instead for now 29 (is(Capacity == ulong) || // two 64-bit words 30 is(Capacity == uint)) && // two 64-bit words 31 isAllocator!Allocator) { 32 33 /** Growth factor P/Q. 34 https://github.com/facebook/folly/blob/master/folly/docs/FBVector.md#memory-handling 35 Use 1.5 like Facebook's `fbvector` does. 36 */ 37 enum _growthP = 3; // numerator 38 /// ditto 39 enum _growthQ = 2; // denominator 40 41 // import core.exception : onOutOfMemoryError; 42 import core.stdc.string : memset; 43 import core.internal.traits : Unqual, hasElaborateDestructor; 44 import std.experimental.allocator : makeArray; 45 import std.range.primitives : isInputRange, ElementType, hasLength, hasSlicing, isInfinite; 46 import std.traits : hasIndirections, hasAliasing, 47 isMutable, TemplateOf, isArray, isType, isIterable, isPointer; 48 import core.lifetime : emplace, move, moveEmplace; 49 50 import nxt.qcmeman : gc_addRange, gc_removeRange; 51 import nxt.container.traits : mustAddGCRange, isRvalueAssignable; 52 53 /// Mutable element type. 54 private alias MT = Unqual!T; 55 56 /// Is `true` if `U` can be assigned to the elements of `this`. 57 enum isElementAssignable(U) = isRvalueAssignable!(MT, U); 58 59 private this(Store store) { 60 version (D_Coverage) {} else pragma(inline, true); 61 _store = store; 62 } 63 64 /// Construct from element `value`. 65 this(U)(U value) @trusted if (isElementAssignable!U) { 66 _store = Store(typeof(this).allocate(1, false), 1); 67 static if (__traits(isPOD, T)) 68 _mptr[0] = value; 69 else 70 moveEmplace(value, _mptr[0]); /+ TODO: remove when compiler does this +/ 71 } 72 73 /// Construct from the element(s) of the dynamic array `values`. 74 this(U)(U[] values) @trusted if (isElementAssignable!(U)) { 75 /+ TODO: use import emplace_all instead +/ 76 _store = Store(allocate(values.length, false), values.length); 77 foreach (index; 0 .. values.length) 78 static if (__traits(isPOD, T)) 79 _mptr[index] = values[index]; 80 else 81 move(values[index], _mptr[index]); 82 } 83 84 /// Construct from the `n` number of element(s) in the static array `values`. 85 this(uint n, U)(U[n] values) @trusted 86 if (values.length <= Capacity.max && isElementAssignable!(U)) { 87 /+ TODO: use import emplace_all instead +/ 88 _store = Store(allocate(values.length, false), values.length, values.length); 89 static foreach (index; 0 .. values.length) 90 static if (__traits(isPOD, T)) 91 _mptr[index] = values[index]; 92 else 93 move(values[index], _mptr[index]); 94 } 95 /// ditto 96 this(R)(scope R values) @trusted 97 if (// isRefIterable!R && 98 isElementAssignable!(ElementType!R) && 99 !isArray!R) { 100 static if (hasLength!R) { 101 reserve(values.length); 102 size_t index = 0; 103 foreach (ref value; values) 104 _mptr[index++] = value; 105 _store._length = values.length; 106 } else 107 foreach (ref value; values) 108 insertBack1(value); 109 } 110 111 /** Is `true` iff the iterable container `C` can be insert to `this`. 112 */ 113 private enum isInsertableContainer(C) = (is(C == struct) && // exclude class ranges for aliasing control 114 isRefIterable!C && // elements may be non-copyable 115 !isInfinite!C && 116 isElementAssignable!(ElementType!C)); 117 118 /// Construct from the elements `values`. 119 static typeof(this) withElementsOfRange_untested(R)(R values) @trusted 120 if (isInsertableContainer!R) { 121 typeof(this) result; 122 static if (hasLength!R) 123 result.reserve(values.length); 124 static if (__traits(isPOD, ElementType!R) && 125 hasLength!R && 126 hasSlicing!R) { 127 import std.algorithm.mutation : copy; 128 copy(values[0 .. values.length], 129 result._mptr[0 .. values.length]); /+ TODO: better to use foreach instead? +/ 130 result._store._length = values.length; 131 } else { 132 static if (hasLength!R) { 133 size_t i = 0; 134 foreach (ref value; move(values)) /+ TODO: remove `move` when compiler does it for us +/ 135 static if (__traits(isPOD, typeof(value))) 136 result._mptr[i++] = value; 137 else 138 moveEmplace(value, result._mptr[i++]); 139 result._store._length = values.length; 140 } else { 141 // import std.algorithm.mutation : moveEmplaceAll; 142 /* TODO: optimize with `moveEmplaceAll` that does a raw copy and 143 * zeroing of values */ 144 foreach (ref value; move(values)) /+ TODO: remove `move` when compiler does it for us +/ 145 static if (__traits(isPOD, ElementType!R)) 146 result.insertBack(value); 147 else 148 result.insertBackMove(value); // steal element 149 } 150 } 151 return result; 152 } 153 154 /// No default copying. 155 this(this) @disable; 156 157 /+ TODO: this gives error in insertBack. why? +/ 158 // void opAssign()(typeof(this) rhs) @trusted pure nothrow @nogc /*tlm*/ 159 // { 160 // move(rhs, this); 161 // } 162 163 /** Destruct. 164 * 165 * TODO: what effect does have here? 166 * See_Also: https://github.com/atilaneves/automem/blob/master/source/automem/vector.d#L92 167 */ 168 ~this() nothrow @nogc /*TODO:scope*/ { 169 releaseElementsStore(); 170 } 171 172 /// Clear. 173 void clear() @nogc { 174 releaseElementsStore(); 175 resetInternalData(); 176 } 177 178 /// Release elements and internal store. 179 private void releaseElementsStore() @nogc @trusted { 180 foreach (const index; 0 .. _store.length) 181 static if (hasElaborateDestructor!T) 182 .destroy(_mptr[index]); 183 else static if (is(T == class) || isPointer!T || hasIndirections!T) 184 _mptr[index] = T.init; // nullify any pointers 185 freeStore(); 186 } 187 188 /// Free internal store. 189 private void freeStore() @trusted { 190 static if (mustAddGCRange!T) 191 gc_removeRange(_mptr); 192 allocator.deallocate(cast(void[])_store[]); 193 } 194 195 /// Reset internal data. 196 private void resetInternalData() @nogc { 197 version (D_Coverage) {} else pragma(inline, true); 198 _store = Store.init; 199 } 200 201 /** Allocate heap region with `initialCapacity` number of elements of type `T`. 202 * 203 * If `initFlag` is `true` elements will be initialized 204 */ 205 private static MT* allocate(in Capacity initialCapacity, in bool initFlag) @trusted { 206 const size_t numBytes = initialCapacity * T.sizeof; 207 typeof(return) ptr = null; 208 if (initFlag) { 209 static if (__traits(isZeroInit, T) && 210 __traits(hasMember, Allocator, "allocateZeroed") && 211 is(typeof(allocator.allocateZeroed(numBytes)) == void[])) 212 ptr = cast(typeof(return))(allocator.allocateZeroed(numBytes).ptr); 213 else { 214 ptr = cast(typeof(return))(allocator.allocate(numBytes).ptr); 215 static if (__traits(isZeroInit, T)) 216 memset(ptr, 0, numBytes); 217 else 218 foreach (i; 0 .. initialCapacity) 219 ptr[i] = MT.init; 220 } 221 } else 222 ptr = cast(typeof(return))(allocator.allocate(numBytes).ptr); 223 if (ptr is null && 224 initialCapacity >= 1 ) 225 /+ TODO: onOutOfMemoryError(); +/ 226 return null; 227 static if (mustAddGCRange!T) 228 gc_addRange(ptr, numBytes); 229 return ptr; 230 } 231 232 static if (__traits(isCopyable, T)) { 233 /** Allocate heap region with `initialCapacity` number of elements of type `T` all set to `elementValue`. 234 */ 235 private static MT* allocateWithValue(in Capacity initialCapacity, T elementValue) @trusted { 236 const size_t numBytes = initialCapacity * T.sizeof; 237 typeof(return) ptr = null; 238 ptr = allocator.makeArray!MT(initialCapacity, elementValue).ptr; /+ TODO: set length +/ 239 if (ptr is null && 240 initialCapacity >= 1) 241 /+ TODO: onOutOfMemoryError(); +/ 242 return null; 243 static if (mustAddGCRange!T) 244 gc_addRange(ptr, numBytes); 245 return ptr; 246 } 247 } 248 249 /** Comparison for equality. */ 250 bool opEquals()(const auto ref typeof(this) rhs) const scope /*tlm*/ { 251 version (D_Coverage) {} else version (LDC) pragma(inline, true); 252 return opSlice() == rhs.opSlice(); 253 } 254 255 /// ditto 256 pragma(inline, true) 257 bool opEquals(U)(const scope U[] rhs) const scope 258 if (is(typeof(T[].init == U[].init))) 259 => opSlice() == rhs; 260 261 /// Calculate D associative array (AA) key hash. 262 pragma(inline, true) 263 hash_t toHash()() const scope @trusted /*tlm*/ 264 => .hashOf(length) + .hashOf(opSlice()); 265 266 static if (__traits(isCopyable, T)) { 267 /** Construct a string representation of `this` at `sink`. */ 268 void toString(Sink)(ref scope Sink sink) const scope /*tlm*/ { 269 import std.conv : to; 270 sink("["); 271 foreach (const index, ref value; opSlice()) { 272 sink(to!string(value)); 273 if (index + 1 < length) { sink(", "); } // separator 274 } 275 sink("]"); 276 } 277 } 278 279 /// Get length. 280 pragma(inline, true) 281 @property Capacity length() const scope => _store.length; 282 /// ditto 283 alias opDollar = length; 284 285 /** Set length to `newLength`. 286 * 287 * If `newLength` < `length` elements are truncate. 288 * If `newLength` > `length` default-initialized elements are appended. 289 */ 290 @property void length(in Capacity newLength) @trusted scope { 291 if (newLength < length) { // if truncation 292 static if (hasElaborateDestructor!T) 293 foreach (const index; newLength .. _store.length) 294 .destroy(_mptr[index]); 295 else static if (mustAddGCRange!T) 296 foreach (const index; newLength .. _store.length) 297 _mptr[index] = T.init; // avoid GC mark-phase dereference 298 } else { 299 reserveFitLength(newLength); 300 static if (hasElaborateDestructor!T) { 301 /+ TODO: remove when compiler does it for us +/ 302 foreach (const index; _store.length .. newLength) { 303 /+ TODO: remove when compiler does it for us: +/ 304 static if (__traits(isCopyable, T)) 305 emplace(&_mptr[index], T.init); 306 else { 307 auto _ = T.init; 308 moveEmplace(_, _mptr[index]); 309 } 310 } 311 } else 312 _mptr[_store.length .. newLength] = T.init; 313 } 314 _store._length = newLength; 315 } 316 317 /// Get capacity. 318 pragma(inline, true) 319 @property Capacity capacity() const scope pure nothrow @nogc => _store.capacity; 320 321 /** Ensures sufficient capacity to accommodate for minimumCapacity number of 322 elements. If `minimumCapacity` < `capacity`, this method does nothing. 323 */ 324 Capacity reserve(in Capacity minimumCapacity) @trusted scope pure nothrow { 325 version (D_Coverage) {} else version (LDC) pragma(inline, true); 326 if (minimumCapacity <= capacity) 327 return capacity; 328 return reallocateAndSetCapacity(_growthP * minimumCapacity / _growthQ); 329 // import std.math.algebraic : nextPow2; 330 // reallocateAndSetCapacity(minimumCapacity.nextPow2); 331 } 332 /** Ensures exactly sufficient capacity to accommodate for minimumCapacity number of 333 elements. If `minimumCapacity` < `capacity`, this method does nothing. 334 */ 335 private Capacity reserveFitLength(in Capacity minimumCapacity) @trusted scope pure nothrow { 336 version (D_Coverage) {} else version (LDC) pragma(inline, true); 337 if (minimumCapacity <= capacity) 338 return capacity; 339 return reallocateAndSetCapacity(minimumCapacity); 340 // import std.math.algebraic : nextPow2; 341 // reallocateAndSetCapacity(minimumCapacity.nextPow2); 342 } 343 344 /// Reallocate storage. 345 private Capacity reallocateAndSetCapacity()(in Capacity newCapacity) @trusted /*tlm*/ { 346 static if (mustAddGCRange!T) 347 gc_removeRange(_store.ptr); 348 349 /+ TODO: functionize: +/ 350 auto slice = cast(void[])(_store[]); 351 const ok = allocator.reallocate(slice, T.sizeof * newCapacity); 352 353 assert(ok); 354 355 _store = Store(cast(T[])slice, _store.length); /+ TODO: only mutate _store.slice +/ 356 357 if (_store.ptr is null && 358 newCapacity >= 1) 359 /+ TODO: onOutOfMemoryError(); +/ 360 return _store.capacity; 361 362 static if (mustAddGCRange!T) 363 gc_addRange(_store.ptr, _store.capacity * T.sizeof); 364 365 return _store.capacity; 366 } 367 368 /// Slice support. 369 pragma(inline, true) 370 inout(T)[] opSlice()(in size_t i, in size_t j) inout return scope @trusted /*tlm*/ => _store.ptr[i .. j]; 371 /// ditto 372 pragma(inline, true) 373 inout(T)[] opSlice()() inout return scope @trusted /*tlm*/ => _store.ptr[0 .. _store.length]; 374 375 /// Slice assignment support. 376 pragma(inline, true) 377 inout(T)[] opSliceAssign(U)(scope U value) inout return scope @trusted => cast(T[])(opSlice()[]) = value; 378 /// ditto 379 pragma(inline, true) 380 inout(T)[] opSliceAssign(U)(scope U value, in size_t i, in size_t j) inout return scope => cast(T[])(opSlice()[i .. j]) = value; 381 382 /// Index support. 383 pragma(inline, true) 384 ref inout(T) opIndex()(in size_t i) inout return scope /*tlm*/ => opSlice()[i]; 385 386 /// Index assignment support. 387 ref T opIndexAssign(U)(scope U value, in size_t i) @trusted return scope { 388 version (D_Coverage) {} else version (LDC) pragma(inline, true); 389 static if (!__traits(isPOD, T)) { 390 move(*(cast(MT*)(&value)), _mptr[i]); /+ TODO: is this correct? +/ 391 return opSlice()[i]; 392 } else static if ((is(T == class) || isPointer!T || hasIndirections!T) && !isMutable!T) 393 static assert(0, "Cannot modify constant elements with indirections"); 394 else 395 return opSlice()[i] = value; 396 } 397 398 /// Get reference to front element. 399 pragma(inline, true) 400 @property ref inout(T) front() inout return scope => opSlice()[0]; // range-checked by default 401 402 /// Get reference to back element. 403 pragma(inline, true) 404 @property ref inout(T) back() inout return scope => opSlice()[_store.length - 1]; // range-checked by default 405 406 /** Insert `value` into the end of the array. 407 */ 408 void insertBack(scope T value) scope @trusted { 409 version (D_Coverage) {} else version (LDC) pragma(inline, true); 410 static if (__traits(isPOD, T)) { 411 reserve(_store.length + 1); 412 _mptr[_store.length] = value; 413 _store._length += 1; 414 } else 415 insertBackMove(*cast(MT*)(&value)); 416 } 417 418 /** Move `value` into the end of the array. 419 */ 420 void insertBackMove(scope ref T value) scope @trusted { 421 version (D_Coverage) {} else version (LDC) pragma(inline, true); 422 reserve(_store.length + 1); 423 static if (__traits(isPOD, T)) 424 _mptr[_store.length] = value; 425 else 426 moveEmplace(value, _mptr[_store.length]); 427 _store._length += 1; 428 } 429 430 alias put = insertBack; 431 432 /** Insert the elements `values` into the end of the array. 433 */ 434 void insertBack(U)(U[] values...) scope @trusted 435 if (isElementAssignable!U && 436 __traits(isCopyable, U)) { // prevent accidental move of l-value `values` 437 if (values.length == 1) /+ TODO: branch should be detected at compile-time +/ 438 // twice as fast as array assignment below 439 return insertBack(values[0]); 440 static if (is(T == immutable(T))) { 441 /* An array of immutable values cannot overlap with the `this` 442 mutable array container data, which entails no need to check for 443 overlap. 444 */ 445 reserve(_store.length + values.length); 446 _mptr[_store.length .. _store.length + values.length] = values; 447 } else { 448 import nxt.overlapping : overlaps; 449 if (_store.ptr == values.ptr) { // called for instances as: `this ~= this` 450 reserve(2*_store.length); // invalidates `values.ptr` 451 foreach (const i; 0 .. _store.length) 452 _mptr[_store.length + i] = _store.ptr[i]; 453 } else if (overlaps(this[], values[])) 454 assert(0, `TODO: Handle overlapping arrays`); 455 else { 456 reserve(_store.length + values.length); 457 _mptr[_store.length .. _store.length + values.length] = values; 458 } 459 } 460 _store._length += values.length; 461 } 462 463 /** Insert the elements `elements` into the end of the array. 464 */ 465 void insertBack(R)(scope R elements) @trusted 466 if (isInsertableContainer!R) { 467 import std.range.primitives : hasLength; 468 static if (isInputRange!R && 469 hasLength!R) { 470 reserve(_store.length + elements.length); 471 import std.algorithm.mutation : copy; 472 copy(elements, _mptr[_store.length .. _store.length + elements.length]); 473 _store.length += elements.length; 474 } else { 475 foreach (ref element; move(elements)) /+ TODO: remove `move` when compiler does it for us +/ 476 static if (__traits(isCopyable, ElementType!R)) 477 insertBack(element); 478 else 479 insertBackMove(element); 480 } 481 } 482 /// ditto 483 alias put = insertBack; 484 485 /** Remove last value from the end of the array. 486 */ 487 void popBack()() @trusted in(length != 0) /*tlm*/ { 488 version (D_Coverage) {} else pragma(inline, true); 489 _store._length -= 1; 490 static if (hasElaborateDestructor!T) 491 .destroy(_mptr[_store.length]); 492 else static if (mustAddGCRange!T) 493 _mptr[_store.length] = T.init; // avoid GC mark-phase dereference 494 } 495 496 /** Rmove `n` last values from the end of the array. 497 498 See_Also: http://mir-algorithm.libmir.org/mir_appender.html#.ScopedBuffer.popBackN 499 */ 500 void popBackN()(in Capacity n) @trusted in(n <= length) /*tlm*/ { 501 _store._length -= n; 502 static if (hasElaborateDestructor!T) 503 foreach (const index; 0 .. n) 504 .destroy(_mptr[_store.length + index]); 505 else static if (mustAddGCRange!T) 506 foreach (const index; 0 .. n) 507 _mptr[_store.length + index] = T.init; // avoid GC mark-phase dereference 508 } 509 510 /** Pop back element and return it. 511 512 This is well-missed feature of C++'s `std::vector` because of problems 513 with exception handling. For more details see 514 https://stackoverflow.com/questions/12600330/pop-back-return-value. 515 */ 516 T takeBack()() @trusted in(length != 0) /*tlm*/ { 517 version (D_Coverage) {} else pragma(inline, true); 518 _store._length -= 1; 519 static if (!__traits(isPOD, T)) 520 return move(_mptr[_store.length]); 521 else static if (is(T == class) || isPointer!T || hasIndirections!T) { // fast, medium, slow path 522 T e = void; 523 moveEmplace(_mptr[_store.length], e); // reset any pointers at `back` 524 return e; 525 } else 526 return _mptr[_store.length]; 527 } 528 529 /** Pop element at `index`. */ 530 void popAt()(in Capacity index) @trusted @("complexity", "O(length)") /*tlm*/ 531 in(index < this.length) { 532 static if (hasElaborateDestructor!T) 533 .destroy(_mptr[index]); 534 else static if (mustAddGCRange!T) 535 _mptr[index] = T.init; // avoid GC mark-phase dereference 536 shiftToFrontAt(index); 537 _store._length -= 1; 538 } 539 540 /** Move element at `index` to return. */ 541 static if (isMutable!T) 542 T moveAt()(in Capacity index) @trusted @("complexity", "O(length)") /*tlm*/ 543 in(index < this.length) { 544 auto value = move(_mptr[index]); 545 shiftToFrontAt(index); 546 _store._length -= 1; 547 return move(value); /+ TODO: remove `move` when compiler does it for us +/ 548 } 549 550 /** Move element at front. */ 551 static if (isMutable!T) 552 pragma(inline, true) 553 T takeFront()() @("complexity", "O(length)") /*tlm*/ 554 => moveAt(0); 555 556 private void shiftToFrontAt()(in Capacity index) @trusted /*tlm*/ { 557 /+ TODO: use this instead: +/ 558 // const si = index + 1; // source index 559 // const ti = index; // target index 560 // const restLength = this.length - (index + 1); 561 // import std.algorithm.mutation : moveEmplaceAll; 562 // moveEmplaceAll(_mptr[si .. si + restLength], 563 // _mptr[ti .. ti + restLength]); 564 foreach (const i; 0 .. this.length - (index + 1)) { // each element index that needs to be moved 565 const si = index + i + 1; // source index 566 const ti = index + i; // target index 567 moveEmplace(_mptr[si], /+ TODO: remove when compiler does this +/ 568 _mptr[ti]); 569 } 570 } 571 572 /** Forwards to $(D insertBack(values)). */ 573 void opOpAssign(string op)(T value) if (op == "~") { 574 version (D_Coverage) {} else pragma(inline, true); 575 insertBackMove(value); 576 } 577 /// ditto 578 void opOpAssign(string op, U)(U[] values...) @trusted if (op == "~" && 579 isElementAssignable!U && 580 __traits(isCopyable, U)) // prevent accidental move of l-value `values` 581 { 582 version (D_Coverage) {} else pragma(inline, true); 583 insertBack(values); 584 } 585 /// ditto 586 void opOpAssign(string op, R)(R values) if (op == "~" && 587 isInputRange!R && 588 !isInfinite!R && 589 !isArray!R && 590 isElementAssignable!(ElementType!R)) { 591 version (D_Coverage) {} else pragma(inline, true); 592 insertBack(values); 593 } 594 595 void opOpAssign(string op)(auto ref typeof(this) values) if (op == "~") { 596 version (D_Coverage) {} else pragma(inline, true); 597 insertBack(values[]); 598 } 599 600 /// Unsafe access to store pointer. 601 pragma(inline, true) 602 @property inout(T)* ptr() inout return @system => _store.ptr; 603 604 /// Convenience access of pointer to mutable store. 605 pragma(inline, true) 606 private @property MT* _mptr() const return @trusted => cast(typeof(return))_store.ptr; 607 608 private: 609 import nxt.allocator_traits : AllocatorState; 610 mixin AllocatorState!Allocator; // put first as emsi-containers do 611 612 import nxt.container.array_store : ArrayStore; 613 alias Store = ArrayStore!(T, Allocator, Capacity); 614 Store _store; 615 } 616 617 import std.functional : unaryFun; 618 619 /** Remove all elements matching `predicate`. 620 621 Returns: number of elements that were removed. 622 623 TODO: implement version that doesn't use a temporary array `tmp`, which is 624 probably faster for small arrays. 625 */ 626 size_t remove(alias predicate, C)(ref C c) @trusted @("complexity", "O(length)") 627 if (is(C == DynamicArray!(_), _...) && 628 is(typeof(unaryFun!predicate(C.init[0])))) { 629 C tmp; 630 size_t count = 0; 631 foreach (const i; 0 .. c.length) { 632 if (unaryFun!predicate(c[i])) { 633 count += 1; 634 import core.internal.traits : hasElaborateDestructor; 635 import nxt.container.traits : mustAddGCRange; 636 alias T = typeof(c[i]); 637 static if (hasElaborateDestructor!(T)) 638 .destroy(c[i]); 639 else static if (mustAddGCRange!(T)) 640 c[i] = T.init; // avoid GC mark-phase dereference 641 } else 642 tmp.insertBackMove(c[i]); /+ TODO: remove unnecessary clearing of `_mptr[i]` +/ 643 } 644 c.freeStore(); 645 import core.lifetime : moveEmplace; 646 moveEmplace(tmp, c); 647 return count; 648 } 649 650 pure nothrow @safe @nogc unittest { 651 alias T = uint; 652 653 DynamicArray!(T, TestAllocator) s; 654 assert(s.length == 0); 655 656 s.insertBack(13U); 657 assert(s.length == 1); 658 assert(s.back == 13); 659 660 s.insertBack(14U); 661 assert(s.length == 2); 662 assert(s.back == 14); 663 664 s.popBack(); 665 assert(s.length == 1); 666 s.popBack(); 667 assert(s.length == 0); 668 } 669 670 pure nothrow @safe @nogc unittest { 671 alias T = int; 672 alias A = DynamicArray!(T, TestAllocator, uint); 673 static if (size_t.sizeof == 8) // for 64-bit 674 static assert(A.sizeof == 2 * size_t.sizeof); // only two words 675 } 676 677 /// construct and append from slices 678 pure nothrow @safe @nogc unittest { 679 alias T = int; 680 alias A = DynamicArray!(T, TestAllocator); 681 auto a = A([10,11,12].s); 682 a ~= a[]; 683 assert(a[] == [10,11,12, 10,11,12].s); 684 a ~= false; 685 assert(a[] == [10,11,12, 10,11,12, 0].s); 686 } 687 688 /// construct and append using self 689 pure nothrow @safe @nogc unittest { 690 alias T = int; 691 alias A = DynamicArray!(T, TestAllocator); 692 auto a = A([10,11,12].s); 693 a ~= a; 694 assert(a[] == [10,11,12, 10,11,12].s); 695 } 696 697 /// 698 pure nothrow @safe @nogc unittest { 699 alias T = int; 700 alias A = DynamicArray!(T, TestAllocator); 701 A a; 702 a.length = 1; 703 assert(a.length == 1); 704 assert(a.capacity == 1); 705 706 a[0] = 10; 707 a.insertBack(11, 12); 708 a ~= T.init; 709 a.insertBack([3].s); 710 assert(a[] == [10,11,12, 0, 3].s); 711 712 import std.algorithm.iteration : filter; 713 714 a.insertBack([42].s[].filter!(_ => _ is 42)); 715 assert(a[] == [10,11,12, 0, 3, 42].s); 716 717 a.insertBack([42].s[].filter!(_ => _ !is 42)); 718 assert(a[] == [10,11,12, 0, 3, 42].s); 719 720 a ~= a[]; 721 assert(a[] == [10,11,12, 0, 3, 42, 722 10,11,12, 0, 3, 42].s); 723 } 724 725 /// 726 pure nothrow @safe @nogc unittest { 727 alias T = int; 728 alias A = DynamicArray!(T); 729 730 A a; // default construction allowed 731 assert(a.length == 0); 732 assert(a.length == 0); 733 assert(a.capacity == 0); 734 assert(a[] == []); 735 736 alias B = DynamicArray!(int, TestAllocator); 737 B b; 738 b.length = 3; 739 assert(b.length != 0); 740 assert(b.length == 3); 741 assert(b.capacity == 3); 742 b[0] = 1; 743 b[1] = 2; 744 b[2] = 3; 745 assert(b[] == [1, 2, 3].s); 746 747 b[] = [4, 5, 6].s; 748 assert(b[] == [4, 5, 6].s); 749 750 auto c = DynamicArray!(int, TestAllocator)(); 751 c.reserve(3); 752 assert(c.length == 0); 753 assert(c.capacity >= 3); 754 assert(c[] == []); 755 756 version (none) // TODO: enable 757 static if (hasPreviewDIP1000) 758 static assert(!__traits(compiles, { T[] f() @safe { A a; return a[]; } })); 759 760 const e = DynamicArray!(int, TestAllocator)([1, 2, 3, 4].s); 761 assert(e.length == 4); 762 assert(e[] == [1, 2, 3, 4].s); 763 } 764 765 /// 766 @trusted pure nothrow @nogc unittest { 767 alias T = int; 768 alias A = DynamicArray!(T, TestAllocator); 769 770 auto a = A([1, 2, 3].s); 771 A b = a.dupShallow; // copy construction enabled 772 773 assert(a[] == b[]); // same content 774 assert(&a[0] !is &b[0]); // but not the same 775 776 assert(b[] == [1, 2, 3].s); 777 assert(b.length == 3); 778 779 b ~= 4; 780 assert(a != b); 781 a.clear(); 782 assert(a != b); 783 b.clear(); 784 assert(a == b); 785 786 const c = A([1, 2, 3].s); 787 assert(c.length == 3); 788 } 789 790 /// DIP-1000 return ref escape analysis 791 pure nothrow @safe @nogc unittest { 792 alias T = int; 793 alias A = DynamicArray!T; 794 version (none) // TODO: enable 795 static if (hasPreviewDIP1000) 796 static assert(!__traits(compiles, { T[] leakSlice() pure nothrow @safe @nogc { A a; return a[]; } })); 797 T* leakPointer() pure nothrow @safe @nogc { 798 A a; 799 return a._store.ptr; /+ TODO: shouldn't compile with -dip1000 +/ 800 } 801 const _lp = leakPointer(); /+ TODO: shouldn't compile with -dip1000 +/ 802 } 803 804 /// construct and insert from non-copyable element type passed by value 805 @safe pure nothrow /*@nogc*/ unittest { 806 alias E = Uncopyable; 807 alias A = DynamicArray!(E); 808 809 A a = A(E(17)); 810 assert(a[] == [E(17)]); 811 812 a.insertBack(E(18)); 813 assert(a[] == [E(17), 814 E(18)]); 815 816 a ~= E(19); 817 assert(a[] == [E(17), 818 E(18), 819 E(19)]); 820 } 821 822 /// construct from slice of uncopyable type 823 pure nothrow @safe @nogc unittest { 824 alias _A = DynamicArray!(Uncopyable); 825 /+ TODO: can we safely support this?: A a = [Uncopyable(17)]; +/ 826 } 827 828 // construct from array with uncopyable elements 829 pure nothrow @safe @nogc unittest { 830 alias A = DynamicArray!(Uncopyable); 831 const A a; 832 assert(a.length == 0); 833 /+ TODO: a.insertBack(A.init); +/ 834 assert(a.length == 0); 835 const _ = a.toHash; 836 } 837 838 // construct from ranges of uncopyable elements 839 pure nothrow @safe @nogc unittest { 840 alias T = Uncopyable; 841 alias A = DynamicArray!T; 842 const A a; 843 assert(a.length == 0); 844 // import std.algorithm.iteration : map, filter; 845 // const b = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => T(_^^2))); // hasLength 846 // assert(b.length == 3); 847 // assert(b == [T(100), T(400), T(900)].s); 848 // const c = A.withElementsOfRange_untested([10, 20, 30].s[].filter!(_ => _ == 30).map!(_ => T(_^^2))); // !hasLength 849 // assert(c.length == 1); 850 // assert(c[0].x == 900); 851 } 852 853 // construct from ranges of copyable elements 854 pure nothrow @safe @nogc unittest { 855 alias T = int; 856 alias A = DynamicArray!(T, TestAllocator); 857 858 const A a; 859 assert(a.length == 0); 860 861 import std.algorithm.iteration : map, filter; 862 863 const b = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => T(_^^2))); // hasLength 864 assert(b.length == 3); 865 assert(b == [T(100), T(400), T(900)].s); 866 867 () @trusted { /+ TODO: remove @trusted +/ 868 const c = A.withElementsOfRange_untested([10, 20, 30].s[].filter!(_ => _ == 30).map!(_ => T(_^^2))); // !hasLength 869 assert(c == [T(900)].s); 870 } (); 871 } 872 873 /// construct with string as element type that needs GC-range 874 pure nothrow @safe @nogc unittest { 875 alias T = string; 876 alias A = DynamicArray!(T, TestAllocator); 877 878 A a; 879 a ~= `alpha`; 880 a ~= `beta`; 881 a ~= [`gamma`, `delta`].s; 882 assert(a[] == [`alpha`, `beta`, `gamma`, `delta`].s); 883 884 const b = [`epsilon`].s; 885 886 a.insertBack(b); 887 assert(a[] == [`alpha`, `beta`, `gamma`, `delta`, `epsilon`].s); 888 889 a ~= b; 890 assert(a[] == [`alpha`, `beta`, `gamma`, `delta`, `epsilon`, `epsilon`].s); 891 } 892 893 /// convert to string 894 version (none) /+ TODO: make this work +/ 895 unittest { 896 alias T = int; 897 alias A = DynamicArray!(T); 898 DynamicArray!char sink; 899 A([1, 2, 3]).toString(sink.put); 900 } 901 902 /// iteration over mutable elements 903 pure nothrow @safe @nogc unittest { 904 alias T = int; 905 alias A = DynamicArray!(T, TestAllocator); 906 auto a = A([1, 2, 3].s); 907 foreach (const i, const e; a) 908 assert(i + 1 == e); 909 } 910 911 /// iteration over `const`ant elements 912 pure nothrow @safe @nogc unittest { 913 alias T = const(int); 914 alias A = DynamicArray!(T, TestAllocator); 915 auto a = A([1, 2, 3].s); 916 foreach (const i, const e; a) 917 assert(i + 1 == e); 918 } 919 920 /// iteration over immutable elements 921 pure nothrow @safe @nogc unittest { 922 alias T = immutable(int); 923 alias A = DynamicArray!(T, TestAllocator); 924 auto a = A([1, 2, 3].s); 925 foreach (const i, const e; a) 926 assert(i + 1 == e); 927 } 928 929 /// removal 930 pure nothrow @safe @nogc unittest { 931 alias T = int; 932 alias A = DynamicArray!(T, TestAllocator); 933 934 auto a = A([1, 2, 3].s); 935 assert(a == [1, 2, 3].s); 936 937 assert(a.takeFront() == 1); 938 assert(a == [2, 3].s); 939 940 a.popAt(1); 941 assert(a == [2].s); 942 943 a.popAt(0); 944 assert(a == []); 945 946 a.insertBack(11); 947 assert(a == [11].s); 948 949 assert(a.takeBack == 11); 950 951 a.insertBack(17); 952 assert(a == [17].s); 953 a.popBack(); 954 assert(a.length == 0); 955 956 a.insertBack([11, 12, 13, 14, 15].s[]); 957 a.popAt(2); 958 assert(a == [11, 12, 14, 15].s); 959 a.popAt(0); 960 assert(a == [12, 14, 15].s); 961 a.popAt(2); 962 963 assert(a == [12, 14].s); 964 965 a ~= a; 966 } 967 968 /// removal 969 pure nothrow @safe unittest { 970 import nxt.container.traits : mustAddGCRange; 971 972 size_t mallocCount = 0; 973 size_t freeCount = 0; 974 975 struct S { 976 pure nothrow @safe @nogc: 977 alias E = int; 978 import nxt.qcmeman : malloc, free; 979 this(E x) @trusted { 980 _ptr = cast(E*)malloc(E.sizeof); 981 mallocCount += 1; 982 *_ptr = x; 983 } 984 this(this) @disable; 985 ~this() nothrow @trusted @nogc { 986 free(_ptr); 987 freeCount += 1; 988 } 989 import nxt.gc_traits : NoGc; 990 @NoGc E* _ptr; 991 } 992 993 /* D compilers cannot currently move stuff efficiently when using 994 std.algorithm.mutation.move. A final dtor call to the cleared sourced is 995 always done. 996 */ 997 const size_t extraDtor = 1; 998 999 alias A = DynamicArray!(S, TestAllocator); 1000 static assert(!mustAddGCRange!A); 1001 alias AA = DynamicArray!(A, TestAllocator); 1002 static assert(!mustAddGCRange!AA); 1003 1004 assert(mallocCount == 0); 1005 1006 { 1007 A a; 1008 a.insertBack(S(11)); 1009 assert(mallocCount == 1); 1010 assert(freeCount == extraDtor + 0); 1011 } 1012 1013 assert(freeCount == extraDtor + 1); 1014 1015 // assert(a.front !is S(11)); 1016 // assert(a.back !is S(11)); 1017 // a.insertBack(S(12)); 1018 } 1019 1020 /// test `OutputRange` behaviour with std.format 1021 version (none) /+ TODO: replace with other exercise of std.format +/ 1022 @safe pure /*TODO: nothrow @nogc*/ unittest { 1023 import std.format : formattedWrite; 1024 const x = "42"; 1025 alias A = DynamicArray!(char); 1026 A a; 1027 a.formattedWrite!("x : %s")(x); 1028 assert(a == "x : 42"); 1029 } 1030 1031 pure nothrow @safe @nogc unittest { 1032 alias T = int; 1033 alias A = DynamicArray!(T, TestAllocator, uint); 1034 const a = A(17); 1035 assert(a[] == [17].s); 1036 } 1037 1038 /// check duplication via `dupShallow` 1039 @trusted pure nothrow @nogc unittest { 1040 alias T = int; 1041 alias A = DynamicArray!(T, TestAllocator); 1042 static assert(!__traits(compiles, { A b = a; })); // copying disabled 1043 auto a = A([10,11,12].s); 1044 auto b = a.dupShallow; 1045 assert(a == b); 1046 assert(&a[0] !is &b[0]); 1047 } 1048 1049 /// element type is a class 1050 pure nothrow @safe unittest { 1051 class T { 1052 this (int x) { this.x = x; } 1053 ~this() nothrow @nogc { x = 42; } 1054 int x; 1055 } 1056 alias A = DynamicArray!(T, TestAllocator); 1057 auto a = A([new T(10), new T(11), new T(12)].s); 1058 assert(a.length == 3); 1059 a.remove!(_ => _.x == 12); 1060 assert(a.length == 2); 1061 } 1062 1063 /// check filtered removal via `remove` 1064 pure nothrow @safe @nogc unittest { 1065 struct T { int value; } 1066 alias A = DynamicArray!(T, TestAllocator); 1067 static assert(!__traits(compiles, { A b = a; })); // copying disabled 1068 1069 auto a = A([T(10), T(11), T(12)].s); 1070 1071 assert(a.remove!"a.value == 13" == 0); 1072 assert(a[] == [T(10), T(11), T(12)].s); 1073 1074 assert(a.remove!"a.value >= 12" == 1); 1075 assert(a[] == [T(10), T(11)].s); 1076 1077 assert(a.remove!(_ => _.value == 10) == 1); 1078 assert(a[] == [T(11)].s); 1079 1080 assert(a.remove!(_ => _.value == 11) == 1); 1081 assert(a.length == 0); 1082 } 1083 1084 /// construct from map range 1085 pure nothrow @safe unittest { 1086 import std.algorithm.iteration : map; 1087 alias T = int; 1088 alias A = DynamicArray!(T, TestAllocator); 1089 1090 A a = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => _^^2)); 1091 assert(a[] == [100, 400, 900].s); 1092 a.popBackN(2); 1093 assert(a.length == 1); 1094 a.popBackN(1); 1095 assert(a.length == 0); 1096 1097 A b = A([10, 20, 30].s[].map!(_ => _^^2)); 1098 assert(b[] == [100, 400, 900].s); 1099 b.popBackN(2); 1100 assert(b.length == 1); 1101 b.popBackN(1); 1102 assert(b.length == 0); 1103 1104 A c = A([10, 20, 30].s[]); 1105 assert(c[] == [10, 20, 30].s); 1106 } 1107 1108 /// construct from map range 1109 @trusted pure nothrow unittest { 1110 alias T = int; 1111 alias A = DynamicArray!(T, TestAllocator); 1112 1113 import std.typecons : RefCounted; 1114 RefCounted!A x; 1115 1116 auto z = [1, 2, 3].s; 1117 x ~= z[]; 1118 1119 auto y = x; 1120 assert(y[] == z); 1121 1122 const _ = x.toHash; 1123 } 1124 1125 /// construct from static array 1126 @trusted pure nothrow @nogc unittest { 1127 alias T = uint; 1128 alias A = DynamicArray!(T, TestAllocator); 1129 1130 ushort[3] a = [1, 2, 3]; 1131 1132 const x = A(a); 1133 assert(x == a); 1134 assert(x == a[]); 1135 } 1136 1137 /// construct from static array slice 1138 @trusted pure nothrow @nogc unittest { 1139 alias T = uint; 1140 alias A = DynamicArray!(T, TestAllocator); 1141 ushort[3] a = [1, 2, 3]; 1142 ushort[] b = a[]; 1143 const y = A(b); // cannot construct directly from `a[]` because its type is `ushort[3]` 1144 assert(y == a); 1145 assert(y == a[]); 1146 } 1147 1148 /// GCAllocator 1149 @trusted pure nothrow unittest { 1150 import std.experimental.allocator.gc_allocator : GCAllocator; 1151 alias T = int; 1152 alias A = DynamicArray!(T, GCAllocator); 1153 const A a; 1154 assert(a.length == 0); 1155 } 1156 1157 /// construct with slices as element types 1158 @trusted pure nothrow unittest { 1159 alias A = DynamicArray!(string); 1160 const A a; 1161 assert(a.length == 0); 1162 alias B = DynamicArray!(char[]); 1163 const B b; 1164 assert(b.length == 0); 1165 } 1166 1167 /** Variant of `DynamicArray` with copy construction (postblit) enabled. 1168 * 1169 * See_Also: suppressing.d 1170 * See_Also: http://forum.dlang.org/post/eitlbtfbavdphbvplnrk@forum.dlang.org 1171 */ 1172 struct BasicCopyableArray { 1173 /** TODO: implement using instructions at: 1174 * http://forum.dlang.org/post/eitlbtfbavdphbvplnrk@forum.dlang.org 1175 */ 1176 } 1177 1178 //+ TODO: Move to Phobos. +/ 1179 private enum bool isRefIterable(T) = is(typeof({ foreach (ref elem; T.init) {} })); 1180 1181 version (unittest) { 1182 import std.experimental.allocator.mallocator : TestAllocator = Mallocator; 1183 import nxt.array_help : s; 1184 import nxt.dip_traits : hasPreviewDIP1000; 1185 import nxt.construction : dupShallow; 1186 import nxt.debugio; 1187 private static struct Uncopyable { this(this) @disable; int _x; } 1188 }