1 /** Cyclic array-like container. 2 * 3 * See_Also: https://en.wikipedia.org/wiki/Sorted_array 4 * 5 * Test: dmd -preview=in -debug -g -unittest -checkaction=context -allinst -main -unittest -I../.. -i -run cyclic.d 6 */ 7 module nxt.container.cyclic; 8 9 import std.algorithm.comparison : max; 10 11 private enum PAGESIZE = 4096; // Linux $(shell getconf PAGESIZE) 12 13 /** Cyclic array-like container. 14 * 15 * Set `capacity_` to `size_t.max` for array with dynamic length. 16 * 17 * TODO: replace PAGESIZE / T.sizeof with standard logic and Allocator 18 * TODO: replace throw staticError!CyclicRangeError with onRangeError 19 * 20 * See_Also: `nxt.container.sorted.Sorted`. 21 */ 22 struct CyclicArray(T, size_t capacity_ = max(8, PAGESIZE / T.sizeof)) 23 { 24 import core.internal.traits : hasElaborateDestructor; 25 import std.traits : isMutable; 26 import std.range.primitives; 27 // TODO: Make the child container type a template parameter and remove this import: 28 import std.container.array : Array; 29 import nxt.container.traits : mustAddGCRange; 30 31 alias capacity = capacity_; // for public use 32 enum isDynamic = capacity == capacity.max; 33 34 private: 35 static if (capacity >= capacity.max) 36 { 37 private size_t reserve(size_t length) nothrow @nogc 38 { 39 assert(length > 0); 40 array.length = length; 41 return length; 42 } 43 Array!T array; 44 } 45 else 46 T[capacity] array; 47 size_t start; 48 size_t size; 49 50 public: 51 static if (isDynamic) 52 { 53 invariant { assert(array.length > 0); } 54 55 // @disable this(); // TODO: this triggers bug in dmd: https://issues.dlang.org/show_bug.cgi?id=20934 56 57 this(Array!T array) @nogc 58 { 59 assert(array.length > 0); 60 this.array = array; 61 } 62 63 this(size_t length) @nogc 64 { 65 assert(length > 0); 66 array = Array!T(); 67 reserve(length); 68 } 69 70 this(size_t n)(T[n] val) 71 { 72 array = Array!T(); 73 reserve(max(8, PAGESIZE / T.sizeof)); 74 static if (capacity != 0) 75 static assert(n <= capacity); 76 foreach (const ref v; val) 77 { 78 put(v); 79 } 80 } 81 82 this(Range)(Range val) 83 if (__traits(compiles, ElementType!Range) && is(ElementType!Range : T)) 84 { 85 array = Array!T(); 86 reserve(max(8, PAGESIZE / T.sizeof)); 87 foreach (const ref v; val) 88 put(v); 89 } 90 91 this(Args...)(Args val) 92 { 93 array = Array!T(); 94 reserve(max(8, PAGESIZE / T.sizeof)); 95 foreach (const ref v; val) 96 put(v); 97 } 98 } 99 else 100 { 101 this(size_t n)(T[n] val) 102 { 103 static if (!isDynamic) 104 static assert(n <= capacity_); 105 foreach (ref v; val) 106 put(v); 107 } 108 109 this(Range)(Range val) 110 if (__traits(compiles, ElementType!Range) && is(ElementType!Range : T)) 111 { 112 foreach (const ref v; val) 113 put(v); 114 } 115 116 this(Args...)(Args val) 117 { 118 foreach (ref v; val) 119 put(v); 120 } 121 } 122 123 mixin CyclicRangePrimitives!(T, q{ 124 static if (capacity_ == size_t.max) 125 auto copy = typeof(cast() this)(array.length); 126 else 127 typeof(cast() this) copy; 128 }); 129 130 static if (!isDynamic) 131 CyclicRange!T byRef() @property @nogc @safe => typeof(return)(array[], start, size); 132 133 size_t length() const @property @nogc @safe => size; 134 135 size_t length(size_t val) @property 136 { 137 if (size > array.length) 138 assert(false); 139 if (val == 0) 140 { 141 clear(); 142 return size; 143 } 144 if (val > size) 145 foreach (const i; size .. val) 146 array[(start + i) % array.length] = T.init; 147 else if (val < size) 148 { 149 static if (hasElaborateDestructor!T) 150 foreach (const i; val .. size) 151 destroy(array[(start + i) % array.length]); 152 else static if (mustAddGCRange!T) 153 foreach (const i; val .. size) 154 array[(start + i) % array.length] = T.init; // avoid GC mark-phase dereference 155 } 156 return size = val; 157 } 158 159 void clear() 160 { 161 static if (hasElaborateDestructor!T) 162 foreach (const i; 0 .. size) 163 destroy(array[(start + i) % array.length]); 164 start = 0; // optimize clears 165 size = 0; 166 } 167 168 auto opBinary(string op : "~")(T rhs) const 169 { 170 auto copy = this.dup; 171 copy.put(rhs); 172 return copy; 173 } 174 175 auto opBinary(string op : "~", size_t n)(T[n] rhs) const 176 { 177 auto copy = this.dup; 178 copy ~= rhs; 179 return copy; 180 } 181 182 auto opBinary(string op : "~", Range)(Range rhs) const 183 if (isInputRange!Range && is(ElementType!Range : T)) 184 { 185 auto copy = this.dup; 186 copy ~= rhs; 187 return copy; 188 } 189 190 void opOpAssign(string op : "~")(T rhs) 191 { 192 put(rhs); 193 } 194 195 void opOpAssign(string op : "~", size_t n)(T[n] rhs) 196 { 197 foreach (const ref c; rhs) 198 put(c); 199 } 200 201 void opOpAssign(string op : "~", size_t n)(CyclicArray!(T, n) rhs) 202 { 203 foreach (const i; 0 .. rhs.size) 204 put(rhs.array[(rhs.start + i) % rhs.array.length]); 205 } 206 207 void opOpAssign(string op : "~", Range)(Range rhs) 208 if (__traits(compiles, ElementType!Range) && is(ElementType!Range : T)) 209 { 210 foreach (const ref c; rhs) 211 put(c); 212 } 213 214 static if (capacity_ == size_t.max) 215 { 216 CyclicArray!(T, capacity_) dup() const 217 { 218 auto ret = CyclicArray!(T, capacity_)(array); 219 ret.start = start; 220 ret.size = size; 221 return ret; 222 } 223 } 224 else 225 { 226 CyclicArray!(T, capacity_) dup() const 227 { 228 CyclicArray!(T, capacity_) ret; 229 ret.array = cast(typeof(ret.array)) array; 230 ret.start = start; 231 ret.size = size; 232 return ret; 233 } 234 } 235 236 static if (!isDynamic) 237 { 238 int opApply(int delegate(ref T) @nogc dg) @nogc 239 { 240 int result = 0; 241 foreach (const i; 0 .. size) 242 { 243 result = dg(array[(i + start) % array.length]); 244 if (result) 245 break; 246 } 247 return result; 248 } 249 250 int opApply(int delegate(size_t, ref T) @nogc dg) @nogc 251 { 252 int result = 0; 253 foreach (const i; 0 .. size) 254 { 255 result = dg(i, array[(i + start) % array.length]); 256 if (result) 257 break; 258 } 259 return result; 260 } 261 } 262 263 bool opEquals(in CyclicArray!(T, capacity_) b) 264 { 265 if (size != b.size) 266 return false; 267 foreach (const i; 0 .. size) 268 if (array[(i + start) % array.length] != 269 b.array[(i + b.start) % b.array.length]) 270 return false; 271 return true; 272 } 273 274 bool opEquals(size_t n)(T[n] b) 275 { 276 if (size != n) 277 return false; 278 foreach (const i; 0 .. size) 279 if (array[(i + start) % array.length] != b[i]) 280 return false; 281 return true; 282 } 283 284 bool opEquals(Range)(Range b) if (hasLength!Range && is(ElementType!Range : T)) 285 { 286 if (size != b.length) 287 return false; 288 auto r = b.save; 289 foreach (const i; 0 .. size) 290 { 291 if (array[(i + start) % array.length] != r.front) 292 return false; 293 r.popFront(); 294 } 295 return true; 296 } 297 } 298 299 struct CyclicRange(T) 300 { 301 T[] array; 302 size_t start, size; 303 mixin CyclicRangePrimitives!T; 304 CyclicRange!T dup() const => cast(CyclicRange!T) this; 305 } 306 307 private mixin template CyclicRangePrimitives(T, string makeCopy = "typeof(cast() this) copy;") 308 { 309 import core.internal.traits : hasElaborateDestructor; 310 import std.traits : isMutable; 311 import nxt.container.traits : mustAddGCRange; 312 313 size_t capacity() const @property @nogc @safe => array.length; 314 size_t length() const @property @nogc @safe => size; 315 316 void put()(auto ref T val) 317 { 318 if (size >= array.length) 319 throw staticError!CyclicRangeError("array capacity overflow", __FILE__, __LINE__); 320 array[(start + size) % array.length] = val; 321 size++; 322 } 323 /// ditto 324 void put()(const auto ref T val) 325 { 326 if (size >= array.length) 327 throw staticError!CyclicRangeError("array capacity overflow", __FILE__, __LINE__); 328 array[(start + size) % array.length] = val; 329 size++; 330 } 331 /// ditto 332 void put(size_t n)(T[n] rhs) 333 { 334 foreach (const ref c; rhs) 335 put(c); 336 } 337 /// ditto 338 void put(Range)(Range rhs) 339 if (__traits(compiles, ElementType!Range) && 340 is(ElementType!Range : T)) 341 { 342 foreach (const ref c; rhs) 343 put(c); 344 } 345 /// ditto 346 alias opOpAssign(string op : "~") = put; 347 alias insertBack = put; 348 alias stableInsertBack = insertBack; 349 350 void insertFront()(auto ref T val) 351 { 352 if (size >= array.length) 353 throw staticError!CyclicRangeError("array capacity overflow", __FILE__, __LINE__); 354 start = (start + array.length - 1) % array.length; 355 array[start] = val; 356 size++; 357 } 358 359 void popFront() 360 { 361 if (size == 0) 362 throw staticError!CyclicRangeError("trying to pop an empty array", __FILE__, __LINE__); 363 static if (hasElaborateDestructor!T) 364 destroy(array[start]); 365 else static if (mustAddGCRange!T) 366 array[start] = T.init; // avoid GC mark-phase dereference 367 start = (start + 1) % array.length; 368 size--; 369 } 370 371 auto save() => this; 372 373 bool empty() @nogc @safe @property const => size == 0; 374 375 ref auto front() @nogc @safe @property inout 376 { 377 if (size == 0) 378 throw staticError!CyclicRangeError("trying to call front on empty array", 379 __FILE__, __LINE__); 380 return array[start]; 381 } 382 383 void popBack() 384 { 385 if (size == 0) 386 throw staticError!CyclicRangeError("trying to pop an empty array", __FILE__, __LINE__); 387 size--; 388 static if (hasElaborateDestructor!T) 389 destroy(array[(start + size) % array.length]); 390 else static if (mustAddGCRange!T) 391 array[(start + size) % array.length] = T.init; // avoid GC mark-phase dereference 392 } 393 394 ref auto back() @property 395 { 396 if (size == 0) 397 throw staticError!CyclicRangeError("trying to call back on empty array", 398 __FILE__, __LINE__); 399 return array[(start + size - 1) % array.length]; 400 } 401 402 auto back() @property const 403 { 404 if (size == 0) 405 throw staticError!CyclicRangeError("trying to call back on empty array", 406 __FILE__, __LINE__); 407 return array[(start + size - 1) % array.length]; 408 } 409 410 size_t opDollar() @nogc @safe const => length; 411 412 ref inout(T) opIndex(size_t v) inout 413 { 414 if (v >= size) 415 throw staticError!CyclicRangeError("out of range", __FILE__, __LINE__); 416 else 417 return array[(v + start) % array.length]; 418 } 419 420 auto opIndex() const => this.dup(); 421 422 private void validateRange(size_t[2] range) 423 { 424 immutable size_t from = range[0]; 425 immutable size_t to = range[1]; 426 if (to > size) 427 throw staticError!CyclicRangeError("trying to slice over array size", 428 __FILE__, __LINE__); 429 if (from > to) 430 throw staticError!CyclicRangeError("trying to negative slice", __FILE__, __LINE__); 431 if (from != to && from >= size || to - from > size) 432 throw staticError!CyclicRangeError("trying to slice over array bounds", 433 __FILE__, __LINE__); 434 } 435 436 auto opIndex(size_t[2] range) 437 { 438 immutable size_t from = range[0]; 439 immutable size_t to = range[1]; 440 validateRange(range); 441 442 mixin(makeCopy); 443 copy.start = 0; 444 copy.size = to - from; 445 446 foreach (const i; 0 .. copy.size) 447 copy.array[i] = array[(i + start + from) % array.length]; 448 return copy; 449 } 450 451 static if (isMutable!T) 452 { 453 void opIndexUnary(string op)() 454 { 455 foreach (const i; 0 .. size) 456 mixin(op ~ "array[(i + start) % array.length];"); 457 } 458 459 auto opIndexUnary(string op)(size_t i) 460 { 461 return mixin(op ~ "array[(i + start) % array.length]"); 462 } 463 464 void opIndexUnary(string op)(size_t[2] range) 465 { 466 immutable size_t from = range[0]; 467 immutable size_t to = range[1]; 468 validateRange(range); 469 470 foreach (const i; 0 .. to - from) 471 mixin(op ~ "array[(i + start + from) % array.length];"); 472 } 473 474 void opIndexAssign(U)(U val) 475 { 476 foreach (const i; 0 .. size) 477 array[(i + start) % array.length] = val; 478 } 479 480 void opIndexAssign(U)(U val, size_t i) 481 { 482 opIndex(i) = val; 483 } 484 485 void opIndexAssign(U)(U val, size_t[2] range) 486 { 487 immutable size_t from = range[0]; 488 immutable size_t to = range[1]; 489 validateRange(range); 490 491 foreach (const i; 0 .. to - from) 492 array[(i + start + from) % array.length] = val; 493 } 494 495 void opIndexOpAssign(string op, U)(U val) 496 { 497 foreach (const i; 0 .. size) 498 mixin("array[(i + start) % array.length] " ~ op ~ "= val;"); 499 } 500 501 void opIndexOpAssign(string op, U)(U val, size_t i) 502 { 503 mixin("array[(i + start) % array.length] " ~ op ~ "= val;"); 504 } 505 506 void opIndexOpAssign(string op, U)(U val, size_t[2] range) 507 { 508 immutable size_t from = range[0]; 509 immutable size_t to = range[1]; 510 validateRange(range); 511 512 foreach (const i; 0 .. to - from) 513 mixin("array[(i + start + from) % array.length] " ~ op ~ "= val;"); 514 } 515 } 516 517 static if (isMutable!T) 518 { 519 import std.algorithm.mutation : move; 520 521 T moveFront() 522 { 523 if (size == 0) 524 throw staticError!CyclicRangeError("trying to move in empty array", 525 __FILE__, __LINE__); 526 return move(array[0]); 527 } 528 529 T moveBack() 530 { 531 if (size == 0) 532 throw staticError!CyclicRangeError("trying to move in empty array", 533 __FILE__, __LINE__); 534 return move(array[(start + size - 1) % array.length]); 535 } 536 537 T moveAt(size_t i) 538 { 539 if (i >= size) 540 throw staticError!CyclicRangeError("trying to move outside range", 541 __FILE__, __LINE__); 542 return move(array[(start + i) % array.length]); 543 } 544 } 545 546 size_t[2] opSlice(size_t i : 0)() const => [0, size]; 547 size_t[2] opSlice(size_t i : 0)(size_t from, size_t to) const => [from, to]; 548 549 /** 550 * Removes the last element from the array and returns it. 551 * Both stable and non-stable versions behave the same and guarantee 552 * that ranges iterating over the array are never invalidated. 553 * 554 * Precondition: `empty == false` 555 * 556 * Returns: The element removed. 557 * 558 * Complexity: $(BIGOH 1). 559 * 560 * Throws: `Exception` if the array is empty. 561 */ 562 T removeAny() 563 { 564 auto result = back; 565 removeBack(); 566 return result; 567 } 568 569 alias stableRemoveAny = removeAny; 570 571 /** 572 * Removes the value from the back of the array. Both stable and non-stable 573 * versions behave the same and guarantee that ranges iterating over the 574 * array are never invalidated. 575 * 576 * Precondition: `empty == false` 577 * 578 * Complexity: $(BIGOH 1). 579 * 580 * Throws: `Exception` if the array is empty. 581 */ 582 void removeBack() => popBack(); 583 584 /// ditto 585 alias stableRemoveBack = removeBack; 586 587 void removeBack(int howMany) 588 { 589 foreach (const i; 0 .. howMany) 590 popBack(); 591 } 592 } 593 594 /// @nogc array without memory management using a cyclic array internally 595 /// Should be treated like a static array when no capacity_ is set (copies on assignment) 596 /// The maximum capacity is static and by default so many elements that it fills at most 4KiB, but at least 8 elements (even if that is more than 4KiB). 597 /// Set length to 0 to make it a std.container.Array based array 598 /// Bugs: foreach with ref value requires @nogc body to make this @nogc compatible 599 /// 600 @nogc @safe unittest 601 { 602 CyclicArray!int array; 603 assert(array.length == 0); 604 array ~= 5; 605 assert(!array.empty); 606 assert(array.front == 5); 607 assert(array[0] == 5); 608 array ~= [4, 3]; 609 610 assert(array == [5, 4, 3]); 611 612 // same efficiency as insertBack/put/concat 613 array.insertFront(5); 614 } 615 616 @nogc @system unittest 617 { 618 // heap array using std.container.Array with 16 elements 619 auto heapArray = CyclicArray!(int, size_t.max)(16); 620 621 // custom memory using Array 622 auto myArray = Array!int(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); 623 auto customArray = CyclicArray!(int, size_t.max)(myArray[0 .. 6]); 624 } 625 626 class CyclicRangeError : Error 627 { 628 @nogc @safe pure nothrow this(string msg, string file = __FILE__, 629 size_t line = __LINE__, Throwable next = null) 630 { 631 super(msg, file, line, next); 632 } 633 } 634 635 // TLS storage shared for all errors, chaining might create circular reference 636 private void[128] _store; 637 638 // only Errors for now as those are rarely chained 639 private T staticError(T, Args...)(auto ref Args args) @trusted if (is(T : Error)) 640 { 641 // pure hack, what we actually need is @noreturn and allow to call that in pure functions 642 static T get() 643 { 644 static assert(__traits(classInstanceSize, T) <= _store.length, 645 T.stringof ~ " is too large for staticError()"); 646 647 _store[0 .. __traits(classInstanceSize, T)] = typeid(T).initializer[]; 648 return cast(T) _store.ptr; 649 } 650 651 auto res = (cast(T function() @trusted pure nothrow @nogc)&get)(); 652 res.__ctor(args); 653 return res; 654 } 655 656 // lots of unittests taken from std.container.array 657 658 // Give the Range object some testing. 659 @system unittest 660 { 661 import std.algorithm.comparison : equal; 662 import std.range : retro; 663 664 auto a = CyclicArray!int(0, 1, 2, 3, 4, 5, 6)[]; 665 assert(equal(a, [0, 1, 2, 3, 4, 5, 6])); 666 assert(equal(a[], [0, 1, 2, 3, 4, 5, 6])); 667 assert(equal(a[0 .. $], [0, 1, 2, 3, 4, 5, 6])); 668 auto b = CyclicArray!int(6, 5, 4, 3, 2, 1, 0)[]; 669 alias A = typeof(a); 670 alias ARef = typeof(a.byRef); 671 672 static assert(isRandomAccessRange!A); 673 static assert(hasSlicing!A); 674 static assert(hasAssignableElements!A); 675 static assert(hasMobileElements!A); 676 677 static assert(isRandomAccessRange!ARef); 678 static assert(hasSlicing!ARef); 679 static assert(hasAssignableElements!ARef); 680 static assert(hasMobileElements!ARef); 681 682 assert(equal(retro(b), a)); 683 assert(a.length == 7); 684 assert(equal(a[1 .. 4], [1, 2, 3])); 685 } 686 687 @system unittest 688 { 689 alias S = structBug5920; 690 uint dMask; 691 692 auto arr = CyclicArray!S(cast(S[])[]); 693 foreach (const i; 0 .. 8) 694 arr.insertBack(S(i, &dMask)); 695 // don't check dMask now as S may be copied multiple times (it's ok?) 696 { 697 assert(arr.length == 8); 698 dMask = 0; 699 arr.length = 6; 700 assert(arr.length == 6); // make sure shrinking calls the d'tor 701 assert(dMask == 0b1100_0000); 702 arr.removeBack(); 703 assert(arr.length == 5); // make sure removeBack() calls the d'tor 704 assert(dMask == 0b1110_0000); 705 arr.removeBack(3); 706 assert(arr.length == 2); // ditto 707 assert(dMask == 0b1111_1100); 708 arr.clear(); 709 assert(arr.length == 0); // make sure clear() calls the d'tor 710 assert(dMask == 0b1111_1111); 711 } 712 assert(dMask == 0b1111_1111); // make sure the d'tor is called once only. 713 } 714 715 @system unittest 716 { 717 auto a = CyclicArray!(int[])([[1, 2], [3, 4]]); 718 assert(a.length == 2); 719 assert(a[0] == [1, 2]); 720 assert(a[1] == [3, 4]); 721 } 722 723 @system unittest 724 { 725 import std.algorithm.comparison : equal; 726 727 //Test "array-wide" operations 728 auto a = CyclicArray!int([0, 1, 2]); //CyclicArray 729 a[] += 5; 730 assert(a[].equal([5, 6, 7])); 731 ++a[]; 732 assert(a[].equal([6, 7, 8])); 733 import std.stdio; 734 735 a[1 .. 3] *= 5; 736 assert(a[].equal([6, 35, 40])); 737 a[0 .. 2] = 0; 738 assert(a[].equal([0, 0, 40])); 739 740 //Test empty array 741 auto a2 = CyclicArray!int.init; 742 ++a2[]; 743 ++a2[0 .. 0]; 744 a2[] = 0; 745 a2[0 .. 0] = 0; 746 a2[] += 0; 747 a2[0 .. 0] += 0; 748 749 //Test "range-wide" operations 750 auto r = CyclicArray!int([0, 1, 2])[]; //CyclicArray.Range 751 r[] += 5; 752 assert(r.equal([5, 6, 7])); 753 ++r[]; 754 assert(r.equal([6, 7, 8])); 755 r[1 .. 3] *= 5; 756 assert(r.equal([6, 35, 40])); 757 r[0 .. 2] = 0; 758 assert(r.equal([0, 0, 40])); 759 760 //Test empty Range 761 auto r2 = CyclicArray!int.init[]; 762 ++r2[]; 763 ++r2[0 .. 0]; 764 r2[] = 0; 765 r2[0 .. 0] = 0; 766 r2[] += 0; 767 r2[0 .. 0] += 0; 768 } 769 770 // Test issue 771 @system unittest 772 { 773 static struct S 774 { 775 int i = 1337; 776 void* p; 777 this(this) 778 { 779 assert(i == 1337); 780 } 781 782 ~this() nothrow @nogc 783 { 784 assert(i == 1337); 785 } 786 } 787 788 CyclicArray!S arr; 789 S s; 790 arr ~= s; 791 arr ~= s; 792 } 793 794 @system unittest 795 { 796 import std.algorithm.iteration : filter; 797 798 auto a = CyclicArray!int([1, 2, 2].filter!"true"()); 799 } 800 801 @safe unittest 802 { 803 auto arr = new CyclicArray!int; 804 } 805 806 @system unittest //6998 807 { 808 static int i = 0; 809 class C 810 { 811 int dummy = 1; 812 this() 813 { 814 ++i; 815 } 816 817 ~this() nothrow @nogc 818 { 819 --i; 820 } 821 } 822 823 assert(i == 0); 824 auto c = new C(); 825 assert(i == 1); 826 827 //scope 828 { 829 auto arr = CyclicArray!C(c); 830 assert(i == 1); 831 } 832 //CyclicArray should not have destroyed the class instance 833 assert(i == 1); 834 835 //Just to make sure the GC doesn't collect before the above test. 836 assert(c.dummy == 1); 837 } 838 839 @system unittest //6998-2 840 { 841 static class C 842 { 843 int i; 844 } 845 846 auto c = new C; 847 c.i = 42; 848 CyclicArray!C a; 849 a ~= c; 850 a.clear; 851 assert(c.i == 42); //fails 852 } 853 854 @nogc: 855 unittest 856 { 857 alias IntArray = CyclicArray!int; 858 alias IntRange = CyclicRange!int; 859 860 static assert(isInputRange!IntArray); 861 static assert(isOutputRange!(IntArray, int)); 862 static assert(isForwardRange!IntArray); 863 static assert(isBidirectionalRange!IntArray); 864 static assert(isRandomAccessRange!IntArray); 865 static assert(hasMobileElements!IntArray); 866 static assert(is(ElementType!IntArray == int)); 867 static assert(hasSwappableElements!IntArray); 868 static assert(hasAssignableElements!IntArray); 869 static assert(hasLvalueElements!IntArray); 870 static assert(hasLength!IntArray); 871 static assert(hasSlicing!IntArray); 872 873 static assert(isInputRange!IntRange); 874 static assert(isOutputRange!(IntRange, int)); 875 static assert(isForwardRange!IntRange); 876 static assert(isBidirectionalRange!IntRange); 877 static assert(isRandomAccessRange!IntRange); 878 static assert(hasMobileElements!IntRange); 879 static assert(is(ElementType!IntRange == int)); 880 static assert(hasSwappableElements!IntRange); 881 static assert(hasAssignableElements!IntRange); 882 static assert(hasLvalueElements!IntRange); 883 static assert(hasLength!IntRange); 884 static assert(hasSlicing!IntRange); 885 886 IntArray array; 887 assert(array.length == 0); 888 assert(array.empty); 889 array ~= 5; 890 assert(!array.empty); 891 assert(array.length == 1); 892 assert(array.front == 5); 893 assert(array[0] == 5); 894 assert(array[0 .. 1].front == 5); 895 assert(array[0 .. 0].empty); 896 array ~= cast(int[2])[4, 3]; 897 assert(array.length == 3); 898 assert(array[1] == 4); 899 assert(array[2] == 3); 900 901 foreach (const i; 0 .. 50000) 902 { 903 array ~= i; 904 array.popFront(); 905 } 906 907 assert(array.length == 3); 908 909 array ~= array; 910 assert(array.length == 6); 911 assert((array ~ array).length == 12); 912 assert(array.length == 6); 913 914 array[5] = 1; 915 assert(array[5] == 1); 916 auto copy = array; 917 copy[5] = 2; 918 assert(array[5] == 1); 919 array[][5] = 1; 920 assert(array[5] == 1); 921 922 foreach (ref v; array.byRef) 923 v = 42; 924 925 assert(array[5] == 42); 926 } 927 928 unittest 929 { 930 alias IntArray = CyclicArray!(int, size_t.max); 931 932 static assert(isInputRange!IntArray); 933 static assert(isOutputRange!(IntArray, int)); 934 static assert(isForwardRange!IntArray); 935 static assert(isBidirectionalRange!IntArray); 936 static assert(isRandomAccessRange!IntArray); 937 static assert(hasMobileElements!IntArray); 938 static assert(is(ElementType!IntArray == int)); 939 static assert(hasSwappableElements!IntArray); 940 static assert(hasAssignableElements!IntArray); 941 static assert(hasLvalueElements!IntArray); 942 static assert(hasLength!IntArray); 943 static assert(hasSlicing!IntArray); 944 945 auto array = IntArray(1024); 946 assert(array.length == 0); 947 assert(array.empty); 948 array ~= 5; 949 assert(!array.empty); 950 assert(array.length == 1); 951 assert(array.front == 5); 952 assert(array[0] == 5); 953 assert(array[0 .. 1].front == 5); 954 assert(array[0 .. 0].empty); 955 array ~= cast(int[2])[4, 3]; 956 assert(array.length == 3); 957 assert(array[1] == 4); 958 assert(array[2] == 3); 959 960 foreach (const i; 0 .. 50000) 961 { 962 array ~= i; 963 array.popFront(); 964 } 965 966 assert(array.length == 3); 967 968 array ~= array; 969 assert(array.length == 6); 970 assert((array ~ array).length == 12); 971 assert(array.length == 6); 972 973 array[5] = 1; 974 assert(array[5] == 1); 975 auto copy = array[]; 976 copy[5] = 2; 977 assert(array[5] == 1); 978 array[][5] = 1; 979 assert(array[5] == 1); 980 } 981 982 @system unittest 983 { 984 CyclicArray!int a; 985 assert(a.empty); 986 } 987 988 @system unittest 989 { 990 CyclicArray!int a; 991 a.length = 10; 992 assert(a.length == 10); 993 assert(a.capacity >= a.length); 994 } 995 996 @system unittest 997 { 998 struct Dumb 999 { 1000 int x = 5; 1001 } 1002 1003 CyclicArray!Dumb a; 1004 a.length = 10; 1005 assert(a.length == 10); 1006 assert(a.capacity >= a.length); 1007 immutable cap = a.capacity; 1008 foreach (ref e; a) 1009 e.x = 10; 1010 a.length = 5; 1011 assert(a.length == 5); 1012 foreach (ref e; a) 1013 assert(e.x == 10); 1014 1015 a.length = 8; 1016 assert(a.length == 8); 1017 assert(Dumb.init.x == 5); 1018 foreach (const i; 0 .. 5) 1019 assert(a[i].x == 10); 1020 foreach (const i; 5 .. a.length) 1021 assert(a[i].x == Dumb.init.x); 1022 1023 a[] = Dumb(1); 1024 a.length = 20; 1025 foreach (const i; 0 .. 8) 1026 assert(a[i].x == 1); 1027 foreach (const i; 8 .. a.length) 1028 assert(a[i].x == Dumb.init.x); 1029 1030 // check if overlapping elements properly initialized 1031 a.length = 1; 1032 a.length = 20; 1033 assert(a[0].x == 1); 1034 foreach (e; a[1 .. $]) 1035 assert(e.x == Dumb.init.x); 1036 } 1037 1038 @system unittest 1039 { 1040 CyclicArray!int a = CyclicArray!int(1, 2, 3); 1041 //a._data._refCountedDebug = true; 1042 auto b = a.dup; 1043 assert(b == CyclicArray!int(1, 2, 3)); 1044 b.front = 42; 1045 assert(b == CyclicArray!int(42, 2, 3)); 1046 assert(a == CyclicArray!int(1, 2, 3)); 1047 } 1048 1049 @system unittest 1050 { 1051 auto a = CyclicArray!int(1, 2, 3); 1052 assert(a.length == 3); 1053 } 1054 1055 @system unittest 1056 { 1057 const CyclicArray!int a = [1, 2]; 1058 1059 assert(a[0] == 1); 1060 assert(a.front == 1); 1061 assert(a.back == 2); 1062 1063 static assert(!__traits(compiles, { a[0] = 1; })); 1064 static assert(!__traits(compiles, { a.front = 1; })); 1065 static assert(!__traits(compiles, { a.back = 1; })); 1066 1067 auto r = a[]; 1068 size_t i; 1069 foreach (e; r) 1070 { 1071 assert(e == i + 1); 1072 i++; 1073 } 1074 } 1075 1076 @system unittest 1077 { 1078 auto a = CyclicArray!int(1, 2, 3); 1079 a[1] *= 42; 1080 assert(a[1] == 84); 1081 } 1082 1083 @system unittest 1084 { 1085 auto a = CyclicArray!int(1, 2, 3); 1086 auto b = CyclicArray!int(11, 12, 13); 1087 auto c = a ~ b; 1088 assert(c == CyclicArray!int(1, 2, 3, 11, 12, 13)); 1089 assert(a ~ b[] == CyclicArray!int(1, 2, 3, 11, 12, 13)); 1090 assert(a ~ [4, 5] == CyclicArray!int(1, 2, 3, 4, 5)); 1091 } 1092 1093 @system unittest 1094 { 1095 auto a = CyclicArray!int(1, 2, 3); 1096 auto b = CyclicArray!int(11, 12, 13); 1097 a ~= b; 1098 assert(a == CyclicArray!int(1, 2, 3, 11, 12, 13)); 1099 } 1100 1101 @system unittest 1102 { 1103 auto a = CyclicArray!int(1, 2, 3, 4); 1104 assert(a.removeAny() == 4); 1105 assert(a == CyclicArray!int(1, 2, 3)); 1106 } 1107 1108 private struct structBug5920 1109 { 1110 int order; 1111 uint* pDestructionMask; 1112 ~this() nothrow @nogc 1113 { 1114 if (pDestructionMask) 1115 *pDestructionMask += 1 << order; 1116 } 1117 } 1118 1119 @system unittest 1120 { 1121 auto a = CyclicArray!int([1, 1]); 1122 a[1] = 0; //Check CyclicArray.opIndexAssign 1123 assert(a[1] == 0); 1124 a[1] += 1; //Check CyclicArray.opIndexOpAssign 1125 assert(a[1] == 1); 1126 1127 //Check CyclicArray.opIndexUnary 1128 ++a[0]; 1129 //a[0]++ //op++ doesn't return, so this shouldn't work, even with 5044 fixed 1130 assert(a[0] == 2); 1131 assert(+a[0] == +2); 1132 assert(-a[0] == -2); 1133 assert(~a[0] == ~2); 1134 1135 auto r = a[]; 1136 r[1] = 0; //Check CyclicArray.Range.opIndexAssign 1137 assert(r[1] == 0); 1138 r[1] += 1; //Check CyclicArray.Range.opIndexOpAssign 1139 assert(r[1] == 1); 1140 1141 //Check CyclicArray.Range.opIndexUnary 1142 ++r[0]; 1143 //r[0]++ //op++ doesn't return, so this shouldn't work, even with 5044 fixed 1144 assert(r[0] == 3); 1145 assert(+r[0] == +3); 1146 assert(-r[0] == -3); 1147 assert(~r[0] == ~3); 1148 } 1149 1150 @safe unittest 1151 { 1152 static struct S 1153 { 1154 bool b; 1155 alias b this; 1156 } 1157 1158 alias A = CyclicArray!S; 1159 alias B = CyclicArray!(shared bool); 1160 } 1161 1162 @system unittest 1163 { 1164 CyclicArray!int ai; 1165 ai ~= 1; 1166 assert(ai.front == 1); 1167 1168 static const arr = [1, 2, 3]; 1169 ai.insertBack(arr); 1170 } 1171 1172 version(unittest) 1173 { 1174 import std.range.primitives; 1175 import std.container.array : Array; 1176 }