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 }