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