1 /// 2 module stdx.collections.array; 3 4 import stdx.collections.common; 5 6 debug(CollectionArray) import std.stdio; 7 8 version(unittest) 9 { 10 import std.experimental.allocator.mallocator; 11 import std.experimental.allocator.building_blocks.stats_collector; 12 import std.experimental.allocator : RCIAllocator, RCISharedAllocator, 13 allocatorObject, sharedAllocatorObject; 14 import std.stdio; 15 16 private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); 17 } 18 19 /// 20 struct Array(T) 21 { 22 import std.experimental.allocator : RCIAllocator, RCISharedAllocator, 23 make, dispose, stateSize; 24 import std.experimental.allocator.building_blocks.affix_allocator; 25 import std.traits : isImplicitlyConvertible, Unqual, isArray; 26 import std.range.primitives : isInputRange, isInfinite, ElementType, hasLength; 27 import std.conv : emplace; 28 import core.atomic : atomicOp; 29 import std.algorithm.mutation : move; 30 31 package T[] _payload; 32 package Unqual!T[] _support; 33 34 private: 35 36 static enum double capacityFactor = 3.0 / 2; 37 static enum initCapacity = 3; 38 39 mixin(allocatorHandler); 40 41 @nogc nothrow pure @trusted 42 void addRef(SupportQual, this Q)(SupportQual support) 43 { 44 assert(support !is null); 45 cast(void) _allocator.opPrefix!("+=")(support, 1); 46 } 47 48 void delRef(Unqual!T[] support) 49 { 50 // Will be optimized away, but the type system infers T's safety 51 if (0) { T t = T.init; } 52 53 assert(support !is null); 54 if (_allocator.opPrefix!("-=")(support, 1) == 0) 55 { 56 () @trusted { dispose(_allocator, support); }(); 57 } 58 } 59 60 static string immutableInsert(StuffType)(string stuff) 61 { 62 static if (hasLength!StuffType) 63 { 64 auto stuffLengthStr = q{ 65 size_t stuffLength = } ~ stuff ~ ".length;"; 66 } 67 else 68 { 69 auto stuffLengthStr = q{ 70 import std.range.primitives : walkLength; 71 size_t stuffLength = walkLength(} ~ stuff ~ ");"; 72 } 73 74 return stuffLengthStr ~ q{ 75 _allocator = immutable AllocatorHandler(allocator); 76 auto tmpSupport = (() @trusted => cast(Unqual!T[])(_allocator.allocate(stuffLength * T.sizeof)))(); 77 assert(stuffLength == 0 || (stuffLength > 0 && tmpSupport !is null)); 78 size_t i = 0; 79 foreach (item; } ~ stuff ~ q{) 80 { 81 (() @trusted => emplace(&tmpSupport[i++], item))(); 82 } 83 _support = (() @trusted => cast(typeof(_support))(tmpSupport))(); 84 _payload = (() @trusted => cast(typeof(_payload))(_support[0 .. stuffLength]))(); 85 if (_support) addRef(_support); 86 }; 87 } 88 89 void destroyUnused() 90 { 91 debug(CollectionArray) 92 { 93 writefln("Array.destroyUnused: begin"); 94 scope(exit) writefln("Array.destroyUnused: end"); 95 } 96 if (_support !is null) 97 { 98 delRef(_support); 99 } 100 } 101 102 public: 103 /** 104 * Constructs a qualified array that will use the provided 105 * allocator object. For `immutable` objects, a `RCISharedAllocator` must 106 * be supplied. 107 * 108 * Params: 109 * allocator = a $(REF RCIAllocator, std,experimental,allocator) or 110 * $(REF RCISharedAllocator, std,experimental,allocator) 111 * allocator object 112 * 113 * Complexity: $(BIGOH 1) 114 */ 115 this(A, this Q)(A allocator) 116 if (!is(Q == shared) 117 && (is(A == RCISharedAllocator) || !is(Q == immutable)) 118 && (is(A == RCIAllocator) || is(A == RCISharedAllocator))) 119 { 120 debug(CollectionArray) 121 { 122 writefln("Array.ctor: begin"); 123 scope(exit) writefln("Array.ctor: end"); 124 } 125 static if (is(Q == immutable) || is(Q == const)) 126 { 127 T[] empty; 128 this(allocator, empty); 129 } 130 else 131 { 132 setAllocator(allocator); 133 } 134 } 135 136 /// 137 static if (is(T == int)) 138 @safe unittest 139 { 140 import std.experimental.allocator : theAllocator, processAllocator; 141 142 auto a = Array!int(theAllocator); 143 auto ca = const Array!int(processAllocator); 144 auto ia = immutable Array!int(processAllocator); 145 } 146 147 /** 148 * Constructs a qualified array out of a number of items. 149 * Because no allocator was provided, the array will use the 150 * $(REF, GCAllocator, std,experimental,allocator,gc_allocator). 151 * 152 * Params: 153 * values = a variable number of items, either in the form of a 154 * list or as a built-in array 155 * 156 * Complexity: $(BIGOH m), where `m` is the number of items. 157 */ 158 this(U, this Q)(U[] values...) 159 if (isImplicitlyConvertible!(U, T)) 160 { 161 this(defaultAllocator!(typeof(this)), values); 162 } 163 164 /// 165 static if (is(T == int)) 166 @safe unittest 167 { 168 import std.algorithm.comparison : equal; 169 170 // Create a list from a list of ints 171 { 172 auto a = Array!int(1, 2, 3); 173 assert(equal(a, [1, 2, 3])); 174 } 175 // Create a list from an array of ints 176 { 177 auto a = Array!int([1, 2, 3]); 178 assert(equal(a, [1, 2, 3])); 179 } 180 // Create a list from a list from an input range 181 { 182 auto a = Array!int(1, 2, 3); 183 auto a2 = Array!int(a); 184 assert(equal(a2, [1, 2, 3])); 185 } 186 } 187 188 /** 189 * Constructs a qualified array out of a number of items 190 * that will use the provided allocator object. 191 * For `immutable` objects, a `RCISharedAllocator` must be supplied. 192 * 193 * Params: 194 * allocator = a $(REF RCIAllocator, std,experimental,allocator) or 195 * $(REF RCISharedAllocator, std,experimental,allocator) 196 * allocator object 197 * values = a variable number of items, either in the form of a 198 * list or as a built-in array 199 * 200 * Complexity: $(BIGOH m), where `m` is the number of items. 201 */ 202 this(A, U, this Q)(A allocator, U[] values...) 203 if (!is(Q == shared) 204 && (is(A == RCISharedAllocator) || !is(Q == immutable)) 205 && (is(A == RCIAllocator) || is(A == RCISharedAllocator)) 206 && isImplicitlyConvertible!(U, T)) 207 { 208 debug(CollectionArray) 209 { 210 writefln("Array.ctor: begin"); 211 scope(exit) writefln("Array.ctor: end"); 212 } 213 static if (is(Q == immutable) || is(Q == const)) 214 { 215 mixin(immutableInsert!(typeof(values))("values")); 216 } 217 else 218 { 219 setAllocator(allocator); 220 insert(0, values); 221 } 222 } 223 224 /** 225 * Constructs a qualified array out of an 226 * $(REF_ALTTEXT input range, isInputRange, std,range,primitives). 227 * Because no allocator was provided, the array will use the 228 * $(REF, GCAllocator, std,experimental,allocator,gc_allocator). 229 * If `Stuff` defines `length`, `Array` will use it to reserve the 230 * necessary amount of memory. 231 * 232 * Params: 233 * stuff = an input range of elements that are implitictly convertible 234 * to `T` 235 * 236 * Complexity: $(BIGOH m), where `m` is the number of elements in the range. 237 */ 238 this(Stuff, this Q)(Stuff stuff) 239 if (isInputRange!Stuff && !isInfinite!Stuff 240 && isImplicitlyConvertible!(ElementType!Stuff, T) 241 && !is(Stuff == T[])) 242 { 243 this(defaultAllocator!(typeof(this)), stuff); 244 } 245 246 /** 247 * Constructs a qualified array out of an 248 * $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 249 * that will use the provided allocator object. 250 * For `immutable` objects, a `RCISharedAllocator` must be supplied. 251 * If `Stuff` defines `length`, `Array` will use it to reserve the 252 * necessary amount of memory. 253 * 254 * Params: 255 * allocator = a $(REF RCIAllocator, std,experimental,allocator) or 256 * $(REF RCISharedAllocator, std,experimental,allocator) 257 * allocator object 258 * stuff = an input range of elements that are implitictly convertible 259 * to `T` 260 * 261 * Complexity: $(BIGOH m), where `m` is the number of elements in the range. 262 */ 263 this(A, Stuff, this Q)(A allocator, Stuff stuff) 264 if (!is(Q == shared) 265 && (is(A == RCISharedAllocator) || !is(Q == immutable)) 266 && (is(A == RCIAllocator) || is(A == RCISharedAllocator)) 267 && isInputRange!Stuff 268 && isImplicitlyConvertible!(ElementType!Stuff, T) 269 && !is(Stuff == T[])) 270 { 271 debug(CollectionArray) 272 { 273 writefln("Array.ctor: begin"); 274 scope(exit) writefln("Array.ctor: end"); 275 } 276 static if (is(Q == immutable) || is(Q == const)) 277 { 278 mixin(immutableInsert!(typeof(stuff))("stuff")); 279 } 280 else 281 { 282 setAllocator(allocator); 283 insert(0, stuff); 284 } 285 } 286 287 this(this) 288 { 289 debug(CollectionArray) 290 { 291 writefln("Array.postblit: begin"); 292 scope(exit) writefln("Array.postblit: end"); 293 } 294 _allocator.bootstrap(); 295 if (_support !is null) 296 { 297 addRef(_support); 298 debug(CollectionArray) writefln("Array.postblit: Array %s has refcount: %s", 299 _support, *prefCount(_support)); 300 } 301 } 302 303 // Immutable ctors 304 // Very important to pass the allocator by ref! (Related to postblit bug) 305 private this(SuppQual, PaylQual, AllocQual, this Qualified)(SuppQual support, 306 PaylQual payload, ref AllocQual _newAllocator) 307 if (is(typeof(_support) : typeof(support))) 308 { 309 _support = support; 310 _payload = payload; 311 // Needs a bootstrap 312 // bootstrap is the equivalent of incRef 313 _newAllocator.bootstrap(); 314 _allocator = _newAllocator; 315 if (_support !is null) 316 { 317 addRef(_support); 318 debug(CollectionArray) writefln("Array.ctor immutable: Array %s has " 319 ~ "refcount: %s", _support, *prefCount(_support)); 320 } 321 } 322 323 ~this() 324 { 325 debug(CollectionArray) 326 { 327 writefln("Array.dtor: Begin for instance %s of type %s", 328 cast(size_t)(&this), typeof(this).stringof); 329 scope(exit) writefln("Array.dtor: End for instance %s of type %s", 330 cast(size_t)(&this), typeof(this).stringof); 331 } 332 destroyUnused(); 333 } 334 335 static if (is(T == int)) 336 nothrow pure @safe unittest 337 { 338 auto a = Array!int(1, 2, 3); 339 340 // Infer safety 341 static assert(!__traits(compiles, () @safe { Array!Unsafe(Unsafe(1)); })); 342 static assert(!__traits(compiles, () @safe { auto a = const Array!Unsafe(Unsafe(1)); })); 343 static assert(!__traits(compiles, () @safe { auto a = immutable Array!Unsafe(Unsafe(1)); })); 344 345 static assert(!__traits(compiles, () @safe { Array!UnsafeDtor(UnsafeDtor(1)); })); 346 static assert(!__traits(compiles, () @safe { auto s = const Array!UnsafeDtor(UnsafeDtor(1)); })); 347 static assert(!__traits(compiles, () @safe { auto s = immutable Array!UnsafeDtor(UnsafeDtor(1)); })); 348 349 // Infer purity 350 static assert(!__traits(compiles, () pure { Array!Impure(Impure(1)); })); 351 static assert(!__traits(compiles, () pure { auto a = const Array!Impure(Impure(1)); })); 352 static assert(!__traits(compiles, () pure { auto a = immutable Array!Impure(Impure(1)); })); 353 354 static assert(!__traits(compiles, () pure { Array!ImpureDtor(ImpureDtor(1)); })); 355 static assert(!__traits(compiles, () pure { auto s = const Array!ImpureDtor(ImpureDtor(1)); })); 356 static assert(!__traits(compiles, () pure { auto s = immutable Array!ImpureDtor(ImpureDtor(1)); })); 357 358 // Infer throwability 359 static assert(!__traits(compiles, () nothrow { Array!Throws(Throws(1)); })); 360 static assert(!__traits(compiles, () nothrow { auto a = const Array!Throws(Throws(1)); })); 361 static assert(!__traits(compiles, () nothrow { auto a = immutable Array!Throws(Throws(1)); })); 362 363 static assert(!__traits(compiles, () nothrow { Array!ThrowsDtor(ThrowsDtor(1)); })); 364 static assert(!__traits(compiles, () nothrow { auto s = const Array!ThrowsDtor(ThrowsDtor(1)); })); 365 static assert(!__traits(compiles, () nothrow { auto s = immutable Array!ThrowsDtor(ThrowsDtor(1)); })); 366 } 367 368 private @nogc nothrow pure @trusted 369 size_t slackFront() const 370 { 371 return _payload.ptr - _support.ptr; 372 } 373 374 private @nogc nothrow pure @trusted 375 size_t slackBack() const 376 { 377 return _support.ptr + _support.length - _payload.ptr - _payload.length; 378 } 379 380 /** 381 * Return the number of elements in the array.. 382 * 383 * Returns: 384 * the length of the array. 385 * 386 * Complexity: $(BIGOH 1). 387 */ 388 @nogc nothrow pure @safe 389 size_t length() const 390 { 391 return _payload.length; 392 } 393 394 /// ditto 395 alias opDollar = length; 396 397 /// 398 static if (is(T == int)) 399 @safe unittest 400 { 401 auto a = Array!int(1, 2, 3); 402 assert(a.length == 3); 403 assert(a[$ - 1] == 3); 404 } 405 406 /** 407 * Set the length of the array to `len`. `len` must be less than or equal 408 * to the `capacity` of the array. 409 * 410 * Params: 411 * len = a positive integer 412 * 413 * Complexity: $(BIGOH 1). 414 */ 415 @nogc nothrow pure @trusted 416 void forceLength(size_t len) 417 { 418 assert(len <= capacity); 419 _payload = cast(T[])(_support[slackFront .. len]); 420 } 421 422 /// 423 static if (is(T == int)) 424 @safe unittest 425 { 426 auto a = Array!int(1, 2, 3); 427 a.forceLength(2); 428 assert(a.length == 2); 429 } 430 431 /** 432 * Get the available capacity of the `array`; this is equal to `length` of 433 * the array plus the available pre-allocated, free, space. 434 * 435 * Returns: 436 * a positive integer denoting the capacity. 437 * 438 * Complexity: $(BIGOH 1). 439 */ 440 @nogc nothrow pure @safe 441 size_t capacity() const 442 { 443 return length + slackBack; 444 } 445 446 /// 447 static if (is(T == int)) 448 @safe unittest 449 { 450 auto a = Array!int(1, 2, 3); 451 a.reserve(10); 452 assert(a.capacity == 10); 453 } 454 455 /** 456 * Reserve enough memory from the allocator to store `n` elements. 457 * If the current `capacity` exceeds `n` nothing will happen. 458 * If `n` exceeds the current `capacity`, an attempt to `expand` the 459 * current array is made. If `expand` is successful, all the expanded 460 * elements are default initialized to `T.init`. If the `expand` fails 461 * a new buffer will be allocated, the old elements of the array will be 462 * copied and the new elements will be default initialized to `T.init`. 463 * 464 * Params: 465 * n = a positive integer 466 * 467 * Complexity: $(BIGOH max(length, n)). 468 */ 469 void reserve(size_t n) 470 { 471 debug(CollectionArray) 472 { 473 writefln("Array.reserve: begin"); 474 scope(exit) writefln("Array.reserve: end"); 475 } 476 477 // Will be optimized away, but the type sistem infers T's safety 478 if (0) { T t = T.init; } 479 480 if (n <= capacity) { return; } 481 482 // TODO: why would we want to overwrite the user-defined allocator? 483 auto a = threadAllocatorObject(); 484 setAllocator(a); 485 486 if (_support && _allocator.opCmpPrefix!"=="(_support, 0)) 487 { 488 void[] buf = _support; 489 if (_allocator.expand(buf, (n - capacity) * T.sizeof)) 490 { 491 const oldLength = _support.length; 492 _support = (() @trusted => cast(Unqual!T[])(buf))(); 493 // Emplace extended buf 494 // TODO: maybe? emplace only if T has indirections 495 foreach (i; oldLength .. _support.length) 496 { 497 emplace(&_support[i]); 498 } 499 return; 500 } 501 else 502 { 503 assert(0, "Array.reserve: Failed to expand array."); 504 } 505 } 506 507 auto tmpSupport = (() @trusted => cast(Unqual!T[])(_allocator.allocate(n * T.sizeof)))(); 508 assert(tmpSupport !is null); 509 for (size_t i = 0; i < tmpSupport.length; ++i) 510 { 511 if (i < _payload.length) 512 { 513 emplace(&tmpSupport[i], _payload[i]); 514 } 515 else 516 { 517 emplace(&tmpSupport[i]); 518 } 519 } 520 //tmpSupport[0 .. _payload.length] = _payload[]; 521 destroyUnused(); 522 _support = tmpSupport; 523 addRef(_support); 524 _payload = (() @trusted => cast(T[])(_support[0 .. _payload.length]))(); 525 assert(capacity >= n); 526 } 527 528 /// 529 static if (is(T == int)) 530 @safe unittest 531 { 532 import std.algorithm.comparison : equal; 533 534 auto stuff = [1, 2, 3]; 535 Array!int a; 536 a.reserve(stuff.length); 537 a ~= stuff; 538 assert(equal(a, stuff)); 539 } 540 541 /** 542 * Inserts the elements of an 543 * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a 544 * variable number of items, at the given `pos`. 545 * 546 * If no allocator was provided when the array was created, the 547 * $(REF, GCAllocator, std,experimental,allocator,gc_allocator) will be used. 548 * If `Stuff` defines `length`, `Array` will use it to reserve the 549 * necessary amount of memory. 550 * 551 * Params: 552 * pos = a positive integer 553 * stuff = an input range of elements that are implitictly convertible 554 * to `T`; a variable number of items either in the form of a 555 * list or as a built-in array 556 * 557 * Returns: 558 * the number of elements inserted 559 * 560 * Complexity: $(BIGOH max(length, pos + m)), where `m` is the number of 561 * elements in the range. 562 */ 563 size_t insert(Stuff)(size_t pos, Stuff stuff) 564 if (!isArray!(typeof(stuff)) && isInputRange!Stuff && !isInfinite!Stuff 565 && isImplicitlyConvertible!(ElementType!Stuff, T)) 566 { 567 debug(CollectionArray) 568 { 569 writefln("Array.insert: begin"); 570 scope(exit) writefln("Array.insert: end"); 571 } 572 573 // Will be optimized away, but the type system infers T's safety 574 if (0) { T t = T.init; } 575 576 auto a = threadAllocatorObject(); 577 setAllocator(a); 578 579 static if (hasLength!Stuff) 580 { 581 size_t stuffLength = stuff.length; 582 } 583 else 584 { 585 import std.range.primitives : walkLength; 586 size_t stuffLength = walkLength(stuff); 587 } 588 if (stuffLength == 0) return 0; 589 590 auto tmpSupport = (() @trusted => cast(Unqual!T[])(_allocator.allocate(stuffLength * T.sizeof)))(); 591 assert(stuffLength == 0 || (stuffLength > 0 && tmpSupport !is null)); 592 for (size_t i = 0; i < tmpSupport.length; ++i) 593 { 594 emplace(&tmpSupport[i]); 595 } 596 597 size_t i = 0; 598 foreach (item; stuff) 599 { 600 tmpSupport[i++] = item; 601 } 602 size_t result = insert(pos, tmpSupport); 603 () @trusted { dispose(_allocator, tmpSupport); }(); 604 return result; 605 } 606 607 /// ditto 608 size_t insert(Stuff)(size_t pos, Stuff[] stuff...) 609 if (isImplicitlyConvertible!(Stuff, T)) 610 { 611 debug(CollectionArray) 612 { 613 writefln("Array.insert: begin"); 614 scope(exit) writefln("Array.insert: end"); 615 } 616 617 // Will be optimized away, but the type system infers T's safety 618 if (0) { T t = T.init; } 619 620 assert(pos <= _payload.length); 621 auto a = threadAllocatorObject(); 622 setAllocator(a); 623 624 if (stuff.length == 0) return 0; 625 if (stuff.length > slackBack) 626 { 627 double newCapacity = capacity ? capacity * capacityFactor : stuff.length; 628 while (newCapacity < capacity + stuff.length) 629 { 630 newCapacity = newCapacity * capacityFactor; 631 } 632 reserve((() @trusted => cast(size_t)(newCapacity))()); 633 } 634 //_support[pos + stuff.length .. _payload.length + stuff.length] = 635 //_support[pos .. _payload.length]; 636 for (size_t i = _payload.length + stuff.length - 1; i >= pos + 637 stuff.length; --i) 638 { 639 // Avoids underflow if payload is empty 640 _support[i] = _support[i - stuff.length]; 641 } 642 _support[pos .. pos + stuff.length] = stuff[]; 643 _payload = (() @trusted => cast(T[])(_support[0 .. _payload.length + stuff.length]))(); 644 return stuff.length; 645 } 646 647 /// 648 static if (is(T == int)) 649 @safe unittest 650 { 651 import std.algorithm.comparison : equal; 652 653 Array!int a; 654 assert(a.empty); 655 656 size_t pos = 0; 657 pos += a.insert(pos, 1); 658 pos += a.insert(pos, [2, 3]); 659 assert(equal(a, [1, 2, 3])); 660 } 661 662 /** 663 * Check whether there are no more references to this array instance. 664 * 665 * Returns: 666 * `true` if this is the only reference to this array instance; 667 * `false` otherwise. 668 * 669 * Complexity: $(BIGOH 1). 670 */ 671 @nogc nothrow pure @safe 672 bool isUnique(this _)() 673 { 674 debug(CollectionArray) 675 { 676 writefln("Array.isUnique: begin"); 677 scope(exit) writefln("Array.isUnique: end"); 678 } 679 680 if (_support !is null) 681 { 682 return cast(bool) _allocator.opCmpPrefix!"=="(_support, 1); 683 } 684 return true; 685 } 686 687 /// 688 static if (is(T == int)) 689 @safe unittest 690 { 691 auto a = Array!int(24, 42); 692 assert(a.isUnique); 693 { 694 auto a2 = a; 695 assert(!a.isUnique); 696 a2.front = 0; 697 assert(a.front == 0); 698 } // a2 goes out of scope 699 assert(a.isUnique); 700 } 701 702 /** 703 * Check if the array is empty. 704 * 705 * Returns: 706 * `true` if there are no elements in the array; `false` otherwise. 707 * 708 * Complexity: $(BIGOH 1). 709 */ 710 @nogc nothrow pure @safe 711 bool empty() const 712 { 713 return length == 0; 714 } 715 716 /// 717 static if (is(T == int)) 718 @safe unittest 719 { 720 Array!int a; 721 assert(a.empty); 722 size_t pos = 0; 723 a.insert(pos, 1); 724 assert(!a.empty); 725 } 726 727 /** 728 * Provide access to the first element in the array. The user must check 729 * that the array isn't `empty`, prior to calling this function. 730 * 731 * Returns: 732 * a reference to the first element. 733 * 734 * Complexity: $(BIGOH 1). 735 */ 736 ref auto front(this _)() 737 { 738 assert(!empty, "Array.front: Array is empty"); 739 return _payload[0]; 740 } 741 742 /// 743 static if (is(T == int)) 744 @safe unittest 745 { 746 auto a = Array!int(1, 2, 3); 747 assert(a.front == 1); 748 a.front = 0; 749 assert(a.front == 0); 750 } 751 752 /** 753 * Advance to the next element in the array. The user must check 754 * that the array isn't `empty`, prior to calling this function. 755 * 756 * Complexity: $(BIGOH 1). 757 */ 758 @nogc nothrow pure @safe 759 void popFront() 760 { 761 debug(CollectionArray) 762 { 763 writefln("Array.popFront: begin"); 764 scope(exit) writefln("Array.popFront: end"); 765 } 766 assert(!empty, "Array.popFront: Array is empty"); 767 _payload = _payload[1 .. $]; 768 } 769 770 /// 771 static if (is(T == int)) 772 @safe unittest 773 { 774 auto stuff = [1, 2, 3]; 775 auto a = Array!int(stuff); 776 size_t i = 0; 777 while (!a.empty) 778 { 779 assert(a.front == stuff[i++]); 780 a.popFront; 781 } 782 assert(a.empty); 783 } 784 785 /** 786 * Advance to the next element in the array. The user must check 787 * that the array isn't `empty`, prior to calling this function. 788 * 789 * This must be used in order to iterate through a `const` or `immutable` 790 * array For a mutable array this is equivalent to calling `popFront`. 791 * 792 * Returns: 793 * an array that starts with the next element in the original array. 794 * 795 * Complexity: $(BIGOH 1). 796 */ 797 Qualified tail(this Qualified)() 798 { 799 debug(CollectionArray) 800 { 801 writefln("Array.tail: begin"); 802 scope(exit) writefln("Array.tail: end"); 803 } 804 assert(!empty, "Array.tail: Array is empty"); 805 806 static if (is(Qualified == immutable) || is(Qualified == const)) 807 { 808 return this[1 .. $]; 809 } 810 else 811 { 812 return .tail(this); 813 } 814 } 815 816 /// 817 static if (is(T == int)) 818 @safe unittest 819 { 820 auto ia = immutable Array!int([1, 2, 3]); 821 assert(ia.tail.front == 2); 822 } 823 824 /** 825 * Eagerly iterate over each element in the array and call `fun` over each 826 * element. This should be used to iterate through `const` and `immutable` 827 * arrays. 828 * 829 * Normally, the entire array is iterated. If partial iteration (early stopping) 830 * is desired, `fun` needs to return a value of type 831 * $(REF Flag, std,typecons)`!"each"` (`Yes.each` to continue iteration, or 832 * `No.each` to stop). 833 * 834 * Params: 835 * fun = unary function to apply on each element of the array. 836 * 837 * Returns: 838 * `Yes.each` if it has iterated through all the elements in the array, 839 * or `No.each` otherwise. 840 * 841 * Complexity: $(BIGOH n). 842 */ 843 template each(alias fun) 844 { 845 import std.typecons : Flag, Yes, No; 846 import std.functional : unaryFun; 847 import stdx.collections.slist : SList; 848 849 Flag!"each" each(this Q)() 850 if (is (typeof(unaryFun!fun(T.init)))) 851 { 852 alias fn = unaryFun!fun; 853 854 auto sl = SList!(const Array!T)(this); 855 while (!sl.empty && !sl.front.empty) 856 { 857 static if (!is(typeof(fn(T.init)) == Flag!"each")) 858 { 859 cast(void) fn(sl.front.front); 860 } 861 else 862 { 863 if (fn(sl.front.front) == No.each) 864 return No.each; 865 } 866 sl ~= sl.front.tail; 867 sl.popFront; 868 } 869 return Yes.each; 870 } 871 } 872 873 /// 874 static if (is(T == int)) 875 @safe unittest 876 { 877 import std.typecons : Flag, Yes, No; 878 879 auto ia = immutable Array!int([1, 2, 3]); 880 881 static bool foo(int x) { return x > 0; } 882 static Flag!"each" bar(int x) { return x > 1 ? Yes.each : No.each; } 883 884 assert(ia.each!foo == Yes.each); 885 assert(ia.each!bar == No.each); 886 } 887 888 //int opApply(int delegate(const ref T) dg) const 889 //{ 890 //if (_payload.length && dg(_payload[0])) return 1; 891 //if (!this.empty) this.tail.opApply(dg); 892 //return 0; 893 //} 894 895 /** 896 * Perform a shallow copy of the array. 897 * 898 * Returns: 899 * a new reference to the current array. 900 * 901 * Complexity: $(BIGOH 1). 902 */ 903 ref auto save(this _)() 904 { 905 debug(CollectionArray) 906 { 907 writefln("Array.save: begin"); 908 scope(exit) writefln("Array.save: end"); 909 } 910 return this; 911 } 912 913 /// 914 static if (is(T == int)) 915 @safe unittest 916 { 917 auto stuff = [1, 2, 3]; 918 auto a = Array!int(stuff); 919 size_t i = 0; 920 921 auto tmp = a.save; 922 while (!tmp.empty) 923 { 924 assert(tmp.front == stuff[i++]); 925 tmp.popFront; 926 } 927 assert(tmp.empty); 928 assert(!a.empty); 929 } 930 931 // TODO: needs to know if _allocator is shared or not 932 // We also need to create a tmp array for all the elements 933 Array!T idup(this Q)(); 934 935 /** 936 * Perform a copy of the array. This will create a new array that will copy 937 * the elements of the current array. This will `NOT` call `dup` on the 938 * elements of the array, regardless if `T` defines it or not. 939 * 940 * Returns: 941 * a new mutable array. 942 * 943 * Complexity: $(BIGOH n). 944 */ 945 Array!T dup(this Q)() 946 { 947 debug(CollectionArray) 948 { 949 writefln("Array.dup: begin"); 950 scope(exit) writefln("Array.dup: end"); 951 } 952 Array!T result; 953 result._allocator = _allocator; 954 955 static if (is(Q == immutable) || is(Q == const)) 956 { 957 result.reserve(length); 958 foreach(i; 0 .. length) 959 { 960 result ~= this[i]; 961 } 962 } 963 else 964 { 965 result.insert(0, this); 966 } 967 return result; 968 } 969 970 /// 971 static if (is(T == int)) 972 @safe unittest 973 { 974 import std.algorithm.comparison : equal; 975 976 auto stuff = [1, 2, 3]; 977 auto a = immutable Array!int(stuff); 978 auto aDup = a.dup; 979 assert(equal(aDup, stuff)); 980 aDup.front = 0; 981 assert(aDup.front == 0); 982 assert(a.front == 1); 983 } 984 985 /** 986 * Return a slice to the current array. This is equivalent to calling 987 * `save`. 988 * 989 * Returns: 990 * an array that references the current array. 991 * 992 * Complexity: $(BIGOH 1) 993 */ 994 Qualified opSlice(this Qualified)() 995 { 996 debug(CollectionArray) 997 { 998 writefln("Array.opSlice(): begin"); 999 scope(exit) writefln("Array.opSlice(): end"); 1000 } 1001 return this.save; 1002 } 1003 1004 /** 1005 * Return a slice to the current array that is bounded by `start` and `end`. 1006 * `start` must be less than or equal to `end` and `end` must be less than 1007 * or equal to `length`. 1008 * 1009 * Returns: 1010 * an array that references the current array. 1011 * 1012 * Params: 1013 * start = a positive integer 1014 * end = a positive integer 1015 * 1016 * Complexity: $(BIGOH 1) 1017 */ 1018 Qualified opSlice(this Qualified)(size_t start, size_t end) 1019 in 1020 { 1021 assert(start <= end && end <= length, 1022 "Array.opSlice(s, e): Invalid bounds: Ensure start <= end <= length"); 1023 } 1024 body 1025 { 1026 debug(CollectionArray) 1027 { 1028 scope(failure) assert(0, "Array.opSlice"); 1029 writefln("Array.opSlice(%d, %d): begin", start, end); 1030 scope(exit) writefln("Array.opSlice(%d, %d): end", start, end); 1031 } 1032 return typeof(this)(_support, _payload[start .. end], _allocator); 1033 } 1034 1035 /// 1036 static if (is(T == int)) 1037 @safe unittest 1038 { 1039 import std.algorithm.comparison : equal; 1040 1041 auto stuff = [1, 2, 3]; 1042 auto a = Array!int(stuff); 1043 assert(equal(a[], stuff)); 1044 assert(equal(a[1 .. $], stuff[1 .. $])); 1045 } 1046 1047 /** 1048 * Provide access to the element at `idx` in the array. 1049 * `idx` must be less than `length`. 1050 * 1051 * Returns: 1052 * a reference to the element found at `idx`. 1053 * 1054 * Params: 1055 * idx = a positive integer 1056 * 1057 * Complexity: $(BIGOH 1). 1058 */ 1059 ref auto opIndex(this _)(size_t idx) 1060 in 1061 { 1062 assert(idx < length, "Array.opIndex: Index out of bounds"); 1063 } 1064 body 1065 { 1066 return _payload[idx]; 1067 } 1068 1069 /// 1070 static if (is(T == int)) 1071 @safe unittest 1072 { 1073 auto a = Array!int([1, 2, 3]); 1074 assert(a[2] == 3); 1075 } 1076 1077 /** 1078 * Apply an unary operation to the element at `idx` in the array. 1079 * `idx` must be less than `length`. 1080 * 1081 * Returns: 1082 * a reference to the element found at `idx`. 1083 * 1084 * Params: 1085 * idx = a positive integer 1086 * 1087 * Complexity: $(BIGOH 1). 1088 */ 1089 ref auto opIndexUnary(string op)(size_t idx) 1090 in 1091 { 1092 assert(idx < length, "Array.opIndexUnary!" ~ op ~ ": Index out of bounds"); 1093 } 1094 body 1095 { 1096 mixin("return " ~ op ~ "_payload[idx];"); 1097 } 1098 1099 /// 1100 static if (is(T == int)) 1101 @safe unittest 1102 { 1103 auto a = Array!int([1, 2, 3]); 1104 int x = --a[2]; 1105 assert(a[2] == 2); 1106 assert(x == 2); 1107 } 1108 1109 /** 1110 * Assign `elem` to the element at `idx` in the array. 1111 * `idx` must be less than `length`. 1112 * 1113 * Returns: 1114 * a reference to the element found at `idx`. 1115 * 1116 * Params: 1117 * elem = an element that is implicitly convertible to `T` 1118 * idx = a positive integer 1119 * 1120 * Complexity: $(BIGOH 1). 1121 */ 1122 ref auto opIndexAssign(U)(U elem, size_t idx) 1123 if (isImplicitlyConvertible!(U, T)) 1124 in 1125 { 1126 assert(idx < length, "Array.opIndexAssign: Index out of bounds"); 1127 } 1128 body 1129 { 1130 return _payload[idx] = elem; 1131 } 1132 1133 /// 1134 static if (is(T == int)) 1135 @safe unittest 1136 { 1137 auto a = Array!int([1, 2, 3]); 1138 a[2] = 2; 1139 assert(a[2] == 2); 1140 (a[2] = 3)++; 1141 assert(a[2] == 4); 1142 } 1143 1144 /** 1145 Assign `elem` to all element in the array. 1146 1147 Returns: 1148 a reference to itself 1149 1150 Params: 1151 elem = an element that is implicitly convertible to `T` 1152 1153 Complexity: $(BIGOH n). 1154 */ 1155 ref auto opIndexAssign(U)(U elem) 1156 if (isImplicitlyConvertible!(U, T)) 1157 body 1158 { 1159 _payload[] = elem; 1160 return this; 1161 } 1162 1163 /// 1164 static if (is(T == int)) 1165 @safe unittest 1166 { 1167 import std.algorithm.comparison : equal; 1168 auto a = Array!int([1, 2, 3]); 1169 a[] = 0; 1170 assert(a.equal([0, 0, 0])); 1171 } 1172 1173 /** 1174 Assign `elem` to the element at `idx` in the array. 1175 `idx` must be less than `length`. 1176 1177 Returns: 1178 a reference to the element found at `idx`. 1179 1180 Params: 1181 elem = an element that is implicitly convertible to `T` 1182 indices = a positive integer 1183 1184 Complexity: $(BIGOH n). 1185 */ 1186 auto opSliceAssign(U)(U elem, size_t start, size_t end) 1187 if (isImplicitlyConvertible!(U, T)) 1188 in 1189 { 1190 assert(start <= end, "Array.opSliceAssign: Index out of bounds"); 1191 assert(end < length, "Array.opSliceAssign: Index out of bounds"); 1192 } 1193 body 1194 { 1195 return _payload[start .. end] = elem; 1196 } 1197 1198 /// 1199 static if (is(T == int)) 1200 @safe unittest 1201 { 1202 import std.algorithm.comparison : equal; 1203 auto a = Array!int([1, 2, 3, 4, 5, 6]); 1204 a[1 .. 3] = 0; 1205 assert(a.equal([1, 0, 0, 4, 5, 6])); 1206 } 1207 1208 /** 1209 * Assign to the element at `idx` in the array the result of 1210 * $(D a[idx] op elem). 1211 * `idx` must be less than `length`. 1212 * 1213 * Returns: 1214 * a reference to the element found at `idx`. 1215 * 1216 * Params: 1217 * elem = an element that is implicitly convertible to `T` 1218 * idx = a positive integer 1219 * 1220 * Complexity: $(BIGOH 1). 1221 */ 1222 ref auto opIndexOpAssign(string op, U)(U elem, size_t idx) 1223 if (isImplicitlyConvertible!(U, T)) 1224 in 1225 { 1226 assert(idx < length, "Array.opIndexOpAssign!" ~ op ~ ": Index out of bounds"); 1227 } 1228 body 1229 { 1230 mixin("return _payload[idx]" ~ op ~ "= elem;"); 1231 } 1232 1233 /// 1234 static if (is(T == int)) 1235 @safe unittest 1236 { 1237 auto a = Array!int([1, 2, 3]); 1238 a[2] += 2; 1239 assert(a[2] == 5); 1240 (a[2] += 3)++; 1241 assert(a[2] == 9); 1242 } 1243 1244 /** 1245 * Create a new array that results from the concatenation of this array 1246 * with `rhs`. 1247 * 1248 * Params: 1249 * rhs = can be an element that is implicitly convertible to `T`, an 1250 * input range of such elements, or another `Array` 1251 * 1252 * Returns: 1253 * the newly created array 1254 * 1255 * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`. 1256 */ 1257 auto ref opBinary(string op, U)(auto ref U rhs) 1258 if (op == "~" && 1259 (is (U : const typeof(this)) 1260 || is (U : T) 1261 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 1262 )) 1263 { 1264 debug(CollectionArray) 1265 { 1266 writefln("Array.opBinary!~: begin"); 1267 scope(exit) writefln("Array.opBinary!~: end"); 1268 } 1269 1270 auto newArray = this.dup(); 1271 static if (is(U : const typeof(this))) 1272 { 1273 foreach(i; 0 .. rhs.length) 1274 { 1275 newArray ~= rhs[i]; 1276 } 1277 } 1278 else 1279 { 1280 newArray.insert(length, rhs); 1281 // Or 1282 // newArray ~= rhs; 1283 } 1284 return newArray; 1285 } 1286 1287 /// 1288 static if (is(T == int)) 1289 @safe unittest 1290 { 1291 import std.algorithm.comparison : equal; 1292 1293 auto a = Array!int(1); 1294 auto a2 = a ~ 2; 1295 1296 assert(equal(a2, [1, 2])); 1297 a.front = 0; 1298 assert(equal(a2, [1, 2])); 1299 } 1300 1301 /** 1302 * Assign `rhs` to this array. The current array will now become another 1303 * reference to `rhs`, unless `rhs` is `null`, in which case the current 1304 * array will become empty. If `rhs` refers to the current array nothing will 1305 * happen. 1306 * 1307 * If there are no more references to the previous array, the previous 1308 * array will be destroyed; this leads to a $(BIGOH n) complexity. 1309 * 1310 * Params: 1311 * rhs = a reference to an array 1312 * 1313 * Returns: 1314 * a reference to this array 1315 * 1316 * Complexity: $(BIGOH n). 1317 */ 1318 auto ref opAssign()(auto ref typeof(this) rhs) 1319 { 1320 debug(CollectionArray) 1321 { 1322 scope(failure) assert(0, "Array.opAssign"); 1323 writefln("Array.opAssign: begin: %s", rhs); 1324 scope(exit) writefln("Array.opAssign: end"); 1325 } 1326 1327 if (rhs._support !is null && _support is rhs._support) 1328 { 1329 if (rhs._payload is _payload) 1330 return this; 1331 } 1332 1333 if (rhs._support !is null) 1334 { 1335 rhs.addRef(rhs._support); 1336 debug(CollectionArray) writefln("Array.opAssign: Array %s has refcount: %s", 1337 rhs._payload, *prefCount(rhs._support)); 1338 } 1339 destroyUnused(); 1340 _support = rhs._support; 1341 _payload = rhs._payload; 1342 _allocator = rhs._allocator; 1343 return this; 1344 } 1345 1346 /// 1347 static if (is(T == int)) 1348 @safe unittest 1349 { 1350 import std.algorithm.comparison : equal; 1351 1352 auto a = Array!int(1); 1353 auto a2 = Array!int(1, 2); 1354 1355 a = a2; // this will free the old a 1356 assert(equal(a, [1, 2])); 1357 a.front = 0; 1358 assert(equal(a2, [0, 2])); 1359 } 1360 1361 static if (is(T == int)) 1362 @safe unittest 1363 { 1364 import std.algorithm.comparison : equal; 1365 auto arr = Array!int(1, 2, 3, 4, 5, 6); 1366 auto arr1 = arr[1 .. $]; 1367 auto arr2 = arr[3 .. $]; 1368 arr1 = arr2; 1369 assert(arr1.equal([4, 5, 6])); 1370 assert(arr2.equal([4, 5, 6])); 1371 } 1372 1373 /** 1374 * Append the elements of `rhs` at the end of the array. 1375 * 1376 * If no allocator was provided when the list was created, the 1377 * $(REF, GCAllocator, std,experimental,allocator,gc_allocator) will be used. 1378 * 1379 * Params: 1380 * rhs = can be an element that is implicitly convertible to `T`, an 1381 * input range of such elements, or another `Array` 1382 * 1383 * Returns: 1384 * a reference to this array 1385 * 1386 * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`. 1387 */ 1388 auto ref opOpAssign(string op, U)(auto ref U rhs) 1389 if (op == "~" && 1390 (is (U == typeof(this)) 1391 || is (U : T) 1392 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 1393 )) 1394 { 1395 debug(CollectionArray) 1396 { 1397 writefln("Array.opOpAssign!~: %s begin", typeof(this).stringof); 1398 scope(exit) writefln("Array.opOpAssign!~: %s end", typeof(this).stringof); 1399 } 1400 insert(length, rhs); 1401 return this; 1402 } 1403 1404 /// 1405 static if (is(T == int)) 1406 @safe unittest 1407 { 1408 import std.algorithm.comparison : equal; 1409 1410 Array!int a; 1411 auto a2 = Array!int(4, 5); 1412 assert(a.empty); 1413 1414 a ~= 1; 1415 a ~= [2, 3]; 1416 assert(equal(a, [1, 2, 3])); 1417 1418 // append an input range 1419 a ~= a2; 1420 assert(equal(a, [1, 2, 3, 4, 5])); 1421 a2.front = 0; 1422 assert(equal(a, [1, 2, 3, 4, 5])); 1423 } 1424 1425 /// 1426 bool opEquals()(auto ref typeof(this) rhs) const 1427 { 1428 import std.algorithm.comparison : equal; 1429 return _support.equal(rhs); 1430 } 1431 1432 /// 1433 static if (is(T == int)) 1434 @safe unittest 1435 { 1436 auto arr1 = Array!int(1, 2); 1437 auto arr2 = Array!int(1, 2); 1438 auto arr3 = Array!int(2, 3); 1439 assert(arr1 == arr2); 1440 assert(arr2 == arr1); 1441 assert(arr1 != arr3); 1442 assert(arr3 != arr1); 1443 assert(arr2 != arr3); 1444 assert(arr3 != arr2); 1445 } 1446 1447 /// 1448 int opCmp(U)(auto ref U rhs) 1449 if (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 1450 { 1451 import std.algorithm.comparison : min, equal; 1452 import std.math : sgn; 1453 import std.range.primitives : empty, front, popFront; 1454 auto r1 = this; 1455 auto r2 = rhs; 1456 for (;!r1.empty && !r2.empty; r1.popFront, r2.popFront) 1457 { 1458 if (r1.front < r2.front) 1459 return -1; 1460 else if (r1.front > r2.front) 1461 return 1; 1462 } 1463 // arrays are equal until here, but it could be that one of them is shorter 1464 if (r1.empty && r2.empty) 1465 return 0; 1466 return r1.empty ? -1 : 1; 1467 } 1468 1469 /// 1470 static if (is(T == int)) 1471 @safe unittest 1472 { 1473 auto arr1 = Array!int(1, 2); 1474 auto arr2 = Array!int(1, 2); 1475 auto arr3 = Array!int(2, 3); 1476 auto arr4 = Array!int(0, 3); 1477 assert(arr1 <= arr2); 1478 assert(arr2 >= arr1); 1479 assert(arr1 < arr3); 1480 assert(arr3 > arr1); 1481 assert(arr4 < arr1); 1482 assert(arr4 < arr3); 1483 assert(arr3 > arr4); 1484 } 1485 1486 static if (is(T == int)) 1487 @safe unittest 1488 { 1489 auto arr1 = Array!int(1, 2); 1490 auto arr2 = [1, 2]; 1491 auto arr3 = Array!int(2, 3); 1492 auto arr4 = [0, 3]; 1493 assert(arr1 <= arr2); 1494 assert(arr2 >= arr1); 1495 assert(arr1 < arr3); 1496 assert(arr3 > arr1); 1497 assert(arr4 < arr1); 1498 assert(arr4 < arr3); 1499 assert(arr3 > arr4); 1500 } 1501 1502 /// 1503 auto toHash() 1504 { 1505 // will be safe with 2.082 1506 return () @trusted { return _support.hashOf; }(); 1507 } 1508 1509 /// 1510 @safe unittest 1511 { 1512 auto arr1 = Array!int(1, 2); 1513 assert(arr1.toHash == Array!int(1, 2).toHash); 1514 arr1 ~= 3; 1515 assert(arr1.toHash == Array!int(1, 2, 3).toHash); 1516 assert(Array!int().toHash == Array!int().toHash); 1517 } 1518 } 1519 1520 version(unittest) private nothrow pure @safe 1521 void testConcatAndAppend(RCIAllocator allocator) 1522 { 1523 import std.algorithm.comparison : equal; 1524 1525 auto a = Array!(int)(allocator, 1, 2, 3); 1526 Array!(int) a2 = Array!(int)(allocator); 1527 1528 auto a3 = a ~ a2; 1529 assert(equal(a3, [1, 2, 3])); 1530 1531 auto a4 = a3; 1532 a3 = a3 ~ 4; 1533 assert(equal(a3, [1, 2, 3, 4])); 1534 a3 = a3 ~ [5]; 1535 assert(equal(a3, [1, 2, 3, 4, 5])); 1536 assert(equal(a4, [1, 2, 3])); 1537 1538 a4 = a3; 1539 a3 ~= 6; 1540 assert(equal(a3, [1, 2, 3, 4, 5, 6])); 1541 a3 ~= [7]; 1542 1543 a3 ~= a3; 1544 assert(equal(a3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7])); 1545 1546 Array!int a5 = Array!(int)(allocator); 1547 a5 ~= [1, 2, 3]; 1548 assert(equal(a5, [1, 2, 3])); 1549 auto a6 = a5; 1550 a5 = a5; 1551 a5[0] = 10; 1552 assert(equal(a5, a6)); 1553 1554 // Test concat with mixed qualifiers 1555 auto a7 = immutable Array!(int)(a5); 1556 assert(a7.front == 10); 1557 a5.front = 1; 1558 assert(a7.front == 10); 1559 auto a8 = a5 ~ a7; 1560 assert(equal(a8, [1, 2, 3, 10, 2, 3])); 1561 1562 auto a9 = const Array!(int)(a5); 1563 auto a10 = a5 ~ a9; 1564 assert(equal(a10, [1, 2, 3, 1, 2, 3])); 1565 } 1566 1567 @safe unittest 1568 { 1569 import std.conv; 1570 SCAlloc statsCollectorAlloc; 1571 { 1572 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1573 () nothrow pure @safe { 1574 testConcatAndAppend(_allocator); 1575 }(); 1576 } 1577 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1578 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1579 ~ to!string(bytesUsed) ~ " bytes"); 1580 } 1581 1582 version(unittest) private nothrow pure @safe 1583 void testSimple(RCIAllocator allocator) 1584 { 1585 import std.algorithm.comparison : equal; 1586 import std.algorithm.searching : canFind; 1587 1588 auto a = Array!int(allocator); 1589 assert(a.empty); 1590 assert(a.isUnique); 1591 1592 size_t pos = 0; 1593 a.insert(pos, 1, 2, 3); 1594 assert(a.front == 1); 1595 assert(equal(a, a)); 1596 assert(equal(a, [1, 2, 3])); 1597 1598 a.popFront(); 1599 assert(a.front == 2); 1600 assert(equal(a, [2, 3])); 1601 1602 a.insert(pos, [4, 5, 6]); 1603 a.insert(pos, 7); 1604 a.insert(pos, [8]); 1605 assert(equal(a, [8, 7, 4, 5, 6, 2, 3])); 1606 1607 a.insert(a.length, 0, 1); 1608 a.insert(a.length, [-1, -2]); 1609 assert(equal(a, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 1610 1611 a.front = 9; 1612 assert(equal(a, [9, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 1613 1614 auto aTail = a.tail; 1615 assert(aTail.front == 7); 1616 aTail.front = 8; 1617 assert(aTail.front == 8); 1618 assert(a.tail.front == 8); 1619 assert(!a.isUnique); 1620 1621 assert(canFind(a, 2)); 1622 assert(!canFind(a, -10)); 1623 } 1624 1625 @safe unittest 1626 { 1627 import std.conv; 1628 SCAlloc statsCollectorAlloc; 1629 { 1630 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1631 () nothrow pure @safe { 1632 testSimple(_allocator); 1633 }(); 1634 } 1635 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1636 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1637 ~ to!string(bytesUsed) ~ " bytes"); 1638 } 1639 1640 version(unittest) private nothrow pure @safe 1641 void testSimpleImmutable(RCIAllocator allocator) 1642 { 1643 import std.algorithm.comparison : equal; 1644 import std.algorithm.searching : canFind; 1645 1646 auto a = Array!(immutable int)(allocator); 1647 assert(a.empty); 1648 1649 size_t pos = 0; 1650 a.insert(pos, 1, 2, 3); 1651 assert(a.front == 1); 1652 assert(equal(a, a)); 1653 assert(equal(a, [1, 2, 3])); 1654 1655 a.popFront(); 1656 assert(a.front == 2); 1657 assert(equal(a, [2, 3])); 1658 assert(a.tail.front == 3); 1659 1660 a.insert(pos, [4, 5, 6]); 1661 a.insert(pos, 7); 1662 a.insert(pos, [8]); 1663 assert(equal(a, [8, 7, 4, 5, 6, 2, 3])); 1664 1665 a.insert(a.length, 0, 1); 1666 a.insert(a.length, [-1, -2]); 1667 assert(equal(a, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2])); 1668 1669 // Cannot modify immutable values 1670 static assert(!__traits(compiles, a.front = 9)); 1671 1672 assert(canFind(a, 2)); 1673 assert(!canFind(a, -10)); 1674 } 1675 1676 @safe unittest 1677 { 1678 import std.conv; 1679 SCAlloc statsCollectorAlloc; 1680 { 1681 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1682 () nothrow pure @safe { 1683 testSimpleImmutable(_allocator); 1684 }(); 1685 } 1686 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1687 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1688 ~ to!string(bytesUsed) ~ " bytes"); 1689 } 1690 1691 version(unittest) private nothrow pure @safe 1692 void testCopyAndRef(RCIAllocator allocator) 1693 { 1694 import std.algorithm.comparison : equal; 1695 1696 auto aFromList = Array!int(allocator, 1, 2, 3); 1697 auto aFromRange = Array!int(allocator, aFromList); 1698 assert(equal(aFromList, aFromRange)); 1699 1700 aFromList.popFront(); 1701 assert(equal(aFromList, [2, 3])); 1702 assert(equal(aFromRange, [1, 2, 3])); 1703 1704 size_t pos = 0; 1705 Array!int aInsFromRange = Array!int(allocator); 1706 aInsFromRange.insert(pos, aFromList); 1707 aFromList.popFront(); 1708 assert(equal(aFromList, [3])); 1709 assert(equal(aInsFromRange, [2, 3])); 1710 1711 Array!int aInsBackFromRange = Array!int(allocator); 1712 aInsBackFromRange.insert(pos, aFromList); 1713 aFromList.popFront(); 1714 assert(aFromList.empty); 1715 assert(equal(aInsBackFromRange, [3])); 1716 1717 auto aFromRef = aInsFromRange; 1718 auto aFromDup = aInsFromRange.dup; 1719 assert(aInsFromRange.front == 2); 1720 aFromRef.front = 5; 1721 assert(aInsFromRange.front == 5); 1722 assert(aFromDup.front == 2); 1723 } 1724 1725 @safe unittest 1726 { 1727 import std.conv; 1728 SCAlloc statsCollectorAlloc; 1729 { 1730 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1731 () nothrow pure @safe { 1732 testCopyAndRef(_allocator); 1733 }(); 1734 } 1735 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1736 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1737 ~ to!string(bytesUsed) ~ " bytes"); 1738 } 1739 1740 version(unittest) private nothrow pure @safe 1741 void testImmutability(RCISharedAllocator allocator) 1742 { 1743 import std.algorithm.comparison : equal; 1744 1745 auto a = immutable Array!(int)(allocator, 1, 2, 3); 1746 auto a2 = a; 1747 auto a3 = a2.save(); 1748 1749 assert(a2.front == 1); 1750 assert(a2[0] == a2.front); 1751 static assert(!__traits(compiles, a2.front = 4)); 1752 static assert(!__traits(compiles, a2.popFront())); 1753 1754 auto a4 = a2.tail; 1755 assert(a4.front == 2); 1756 static assert(!__traits(compiles, a4 = a4.tail)); 1757 1758 // Create a mutable copy from an immutable array 1759 auto a5 = a.dup(); 1760 assert(equal(a5, [1, 2, 3])); 1761 assert(a5.front == 1); 1762 a5.front = 2; 1763 assert(a5.front == 2); 1764 assert(a.front == 1); 1765 assert(equal(a5, [2, 2, 3])); 1766 } 1767 1768 version(unittest) private nothrow pure @safe 1769 void testConstness(RCISharedAllocator allocator) 1770 { 1771 auto a = const Array!(int)(allocator, 1, 2, 3); 1772 auto a2 = a; 1773 auto a3 = a2.save(); 1774 1775 assert(a2.front == 1); 1776 assert(a2[0] == a2.front); 1777 static assert(!__traits(compiles, a2.front = 4)); 1778 static assert(!__traits(compiles, a2.popFront())); 1779 1780 auto a4 = a2.tail; 1781 assert(a4.front == 2); 1782 static assert(!__traits(compiles, a4 = a4.tail)); 1783 } 1784 1785 @safe unittest 1786 { 1787 import std.conv; 1788 import std.experimental.allocator : processAllocator; 1789 SCAlloc statsCollectorAlloc; 1790 // TODO: StatsCollector needs to be made shareable 1791 //auto _allocator = sharedAllocatorObject(&statsCollectorAlloc); 1792 1793 () nothrow pure @safe { 1794 testImmutability(processAllocatorObject()); 1795 testConstness(processAllocatorObject()); 1796 }(); 1797 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1798 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1799 ~ to!string(bytesUsed) ~ " bytes"); 1800 } 1801 1802 version(unittest) private nothrow pure @safe 1803 void testWithStruct(RCIAllocator allocator, RCISharedAllocator sharedAlloc) 1804 { 1805 import std.algorithm.comparison : equal; 1806 1807 auto array = Array!int(allocator, 1, 2, 3); 1808 { 1809 auto arrayOfArrays = Array!(Array!int)(allocator, array); 1810 assert(equal(arrayOfArrays.front, [1, 2, 3])); 1811 arrayOfArrays.front.front = 2; 1812 assert(equal(arrayOfArrays.front, [2, 2, 3])); 1813 static assert(!__traits(compiles, arrayOfArrays.insert(1))); 1814 1815 auto immArrayOfArrays = immutable Array!(Array!int)(sharedAlloc, array); 1816 assert(immArrayOfArrays.front.front == 2); 1817 static assert(!__traits(compiles, immArrayOfArrays.front.front = 2)); 1818 } 1819 assert(equal(array, [2, 2, 3])); 1820 } 1821 1822 @safe unittest 1823 { 1824 import std.conv; 1825 import std.experimental.allocator : processAllocator; 1826 SCAlloc statsCollectorAlloc; 1827 { 1828 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1829 () nothrow pure @safe { 1830 testWithStruct(_allocator, processAllocatorObject()); 1831 }(); 1832 } 1833 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1834 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1835 ~ to!string(bytesUsed) ~ " bytes"); 1836 } 1837 1838 version(unittest) private nothrow pure @safe 1839 void testWithClass(RCIAllocator allocator) 1840 { 1841 class MyClass 1842 { 1843 int x; 1844 this(int x) { this.x = x; } 1845 } 1846 1847 MyClass c = new MyClass(10); 1848 { 1849 auto a = Array!MyClass(allocator, c); 1850 assert(a.front.x == 10); 1851 assert(a.front is c); 1852 a.front.x = 20; 1853 } 1854 assert(c.x == 20); 1855 } 1856 1857 @safe unittest 1858 { 1859 import std.conv; 1860 SCAlloc statsCollectorAlloc; 1861 { 1862 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1863 () nothrow pure @safe { 1864 testWithClass(_allocator); 1865 }(); 1866 } 1867 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1868 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1869 ~ to!string(bytesUsed) ~ " bytes"); 1870 } 1871 1872 version(unittest) private nothrow pure @safe 1873 void testOpOverloads(RCIAllocator allocator) 1874 { 1875 auto a = Array!int(allocator, 1, 2, 3, 4); 1876 assert(a[0] == 1); // opIndex 1877 1878 // opIndexUnary 1879 ++a[0]; 1880 assert(a[0] == 2); 1881 --a[0]; 1882 assert(a[0] == 1); 1883 a[0]++; 1884 assert(a[0] == 2); 1885 a[0]--; 1886 assert(a[0] == 1); 1887 1888 // opIndexAssign 1889 a[0] = 2; 1890 assert(a[0] == 2); 1891 1892 // opIndexOpAssign 1893 a[0] /= 2; 1894 assert(a[0] == 1); 1895 a[0] *= 2; 1896 assert(a[0] == 2); 1897 a[0] -= 1; 1898 assert(a[0] == 1); 1899 a[0] += 1; 1900 assert(a[0] == 2); 1901 } 1902 1903 @safe unittest 1904 { 1905 import std.conv; 1906 SCAlloc statsCollectorAlloc; 1907 { 1908 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1909 () nothrow pure @safe { 1910 testOpOverloads(_allocator); 1911 }(); 1912 } 1913 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1914 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1915 ~ to!string(bytesUsed) ~ " bytes"); 1916 } 1917 1918 version(unittest) private nothrow pure @safe 1919 void testSlice(RCIAllocator allocator) 1920 { 1921 import std.algorithm.comparison : equal; 1922 1923 auto a = Array!int(allocator, 1, 2, 3, 4); 1924 auto b = a[]; 1925 assert(equal(a, b)); 1926 b[1] = 5; 1927 assert(a[1] == 5); 1928 1929 size_t startPos = 2; 1930 auto c = b[startPos .. $]; 1931 assert(equal(c, [3, 4])); 1932 c[0] = 5; 1933 assert(equal(a, b)); 1934 assert(equal(a, [1, 5, 5, 4])); 1935 assert(a.capacity == b.capacity && b.capacity == c.capacity + startPos); 1936 1937 c ~= 5; 1938 assert(equal(c, [5, 4, 5])); 1939 assert(equal(a, b)); 1940 assert(equal(a, [1, 5, 5, 4])); 1941 } 1942 1943 @safe unittest 1944 { 1945 import std.conv; 1946 SCAlloc statsCollectorAlloc; 1947 { 1948 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 1949 () nothrow pure @safe { 1950 testSlice(_allocator); 1951 }(); 1952 } 1953 auto bytesUsed = statsCollectorAlloc.bytesUsed; 1954 assert(bytesUsed == 0, "Array ref count leaks memory; leaked " 1955 ~ to!string(bytesUsed) ~ " bytes"); 1956 } 1957 1958 /*@nogc*/ nothrow pure @safe 1959 unittest 1960 { 1961 Array!int a; 1962 auto b = Array!int(1, 2, 3); 1963 }