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 }