The OpenD Programming Language

1 /**
2  * Cyclic Buffer
3  * Copyright: © 2016 Economic Modeling Specialists, Intl.
4  * Authors: Nickolay Bukreyev
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6  */
7 
8 module containers.cyclicbuffer;
9 
10 private import core.exception : onRangeError;
11 private import std.experimental.allocator.mallocator : Mallocator;
12 private import std.range.primitives : empty, front, back, popFront, popBack;
13 private import containers.internal.node : shouldAddGCRange;
14 
15 /**
16  * Array that provides constant time (amortized) appending and popping
17  * at either end, as well as random access to the elements.
18  *
19  * Params:
20  *     T = the array element type
21  *     Allocator = the allocator to use. Defaults to `Mallocator`.
22  *     supportGC = true if the container should support holding references to GC-allocated memory.
23  */
24 struct CyclicBuffer(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T)
25 {
26 	@disable this(this);
27 
28 	private import std.conv : emplace;
29 	private import std.experimental.allocator.common : stateSize;
30 	private import std.traits : isImplicitlyConvertible, hasElaborateDestructor;
31 
32 	static if (stateSize!Allocator != 0)
33 	{
34 		/// No default construction if an allocator must be provided.
35 		@disable this();
36 
37 		/**
38 		 * Use the given `allocator` for allocations.
39 		 */
40 		this(Allocator allocator) nothrow pure @safe @nogc
41 		in
42 		{
43 			static if (is(typeof(allocator is null)))
44 				assert(allocator !is null, "Allocator must not be null");
45 		}
46 		do
47 		{
48 			this.allocator = allocator;
49 		}
50 	}
51 
52 	~this()
53 	{
54 		clear();
55 		static if (useGC)
56 		{
57 			import core.memory : GC;
58 			GC.removeRange(storage.ptr);
59 		}
60 		allocator.deallocate(storage);
61 	}
62 
63 	/**
64 	 * Removes all contents from the buffer.
65 	 */
66 	void clear()
67 	{
68 		if (!empty)
69 		{
70 			static if (hasElaborateDestructor!T)
71 			{
72 				if (start <= end)
73 					foreach (ref item; storage[start .. end + 1])
74 						.destroy(item);
75 				else
76 				{
77 					foreach (ref item; storage[start .. $])
78 						.destroy(item);
79 					foreach (ref item; storage[0 .. end + 1])
80 						.destroy(item);
81 				}
82 			}
83 			start = (end + 1) % capacity;
84 			_length = 0;
85 		}
86 	}
87 
88 	/**
89 	 * Ensures capacity is at least as large as specified.
90 	 */
91 	size_t reserve(size_t newCapacity)
92 	{
93 		immutable oldCapacity = capacity;
94 		if (newCapacity <= oldCapacity)
95 			return oldCapacity;
96 		auto old = storage;
97 		if (oldCapacity == 0)
98 			storage = cast(typeof(storage)) allocator.allocate(newCapacity * T.sizeof);
99 		else
100 		{
101 			auto a = cast(void[]) old;
102 			allocator.reallocate(a, newCapacity * T.sizeof);
103 			storage = cast(typeof(storage)) a;
104 		}
105 		static if (useGC)
106 		{
107 			import core.memory : GC;
108 			//Add, then remove. Exactly in that order.
109 			GC.addRange(storage.ptr, newCapacity * T.sizeof);
110 			GC.removeRange(old.ptr);
111 		}
112 		if (empty)
113 			end = (start - 1 + capacity) % capacity;
114 		else if (start > end)
115 		{
116 			//The buffer was wrapped around prior to reallocation.
117 
118 			//`moveEmplaceAll` is only available in 2.069+, so use a low level alternative.
119 			//Even more, we don't have to .init the moved away data, because we don't .destroy it.
120 			import core.stdc.string : memcpy, memmove;
121 			immutable prefix = end + 1;
122 			immutable suffix = oldCapacity - start;
123 			if (prefix <= suffix)
124 			{
125 				//The prefix is being moved right behind of suffix.
126 				immutable space = newCapacity - oldCapacity;
127 				if (space >= prefix)
128 				{
129 					memcpy(storage.ptr + oldCapacity, storage.ptr, prefix * T.sizeof);
130 					end += oldCapacity;
131 				}
132 				else
133 				{
134 					//There is not enough space, so move what we can,
135 					//and shift the rest to the start of the buffer.
136 					memcpy(storage.ptr + oldCapacity, storage.ptr, space * T.sizeof);
137 					end -= space;
138 					memmove(storage.ptr, storage.ptr + space, (end + 1) * T.sizeof);
139 				}
140 			}
141 			else
142 			{
143 				//The suffix is being moved forward, to the end of the buffer.
144 				//Due to the fact that these locations may overlap, use `memmove`.
145 				memmove(storage.ptr + newCapacity - suffix, storage.ptr + start, suffix * T.sizeof);
146 				start = newCapacity - suffix;
147 			}
148 			//Ensure everything is still alright.
149 			if (start <= end)
150 				assert(end + 1 - start == length);
151 			else
152 				assert(end + 1 + (newCapacity - start) == length);
153 		}
154 		return capacity;
155 	}
156 
157 	/**
158 	 * Inserts the given item into the start of the buffer.
159 	 */
160 	void insertFront(U)(U value) if (isImplicitlyConvertible!(U, T))
161 	{
162 		if (empty)
163 			reserve(4);
164 		else if ((end + 1) % capacity == start)
165 			reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2);
166 		start = (start - 1 + capacity) % capacity;
167 		_length++;
168 		emplace(&storage[start], value);
169 	}
170 
171 	/**
172 	 * Inserts the given item into the end of the buffer.
173 	 */
174 	void insertBack(U)(U value) if (isImplicitlyConvertible!(U, T))
175 	{
176 		if (empty)
177 			reserve(4);
178 		else if ((end + 1) % capacity == start)
179 			reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2);
180 		end = (end + 1) % capacity;
181 		_length++;
182 		emplace(&storage[end], value);
183 	}
184 
185 	/// ditto
186 	alias insert = insertBack;
187 
188 	/// ditto
189 	alias insertAnywhere = insertBack;
190 
191 	/// ditto
192 	alias put = insertBack;
193 
194 	/**
195 	 * Removes the item at the start of the buffer.
196 	 */
197 	void removeFront()
198 	{
199 		version (assert) if (empty) onRangeError();
200 		size_t pos = start;
201 		start = (start + 1) % capacity;
202 		_length--;
203 		static if (hasElaborateDestructor!T)
204 			.destroy(storage[pos]);
205 	}
206 
207 	/// ditto
208 	alias popFront = removeFront;
209 
210 	/**
211 	 * Removes the item at the end of the buffer.
212 	 */
213 	void removeBack()
214 	{
215 		version (assert) if (empty) onRangeError();
216 		size_t pos = end;
217 		end = (end - 1 + capacity) % capacity;
218 		_length--;
219 		static if (hasElaborateDestructor!T)
220 			.destroy(storage[pos]);
221 	}
222 
223 	/// ditto
224 	alias popBack = removeBack;
225 
226 	/// Accesses to the item at the start of the buffer.
227 	auto ref front(this This)() nothrow pure @property @safe
228 	{
229 		version (assert) if (empty) onRangeError();
230 		alias ET = ContainerElementType!(This, T, true);
231 		return cast(ET) storage[start];
232 	}
233 
234 	/// Accesses to the item at the end of the buffer.
235 	auto ref back(this This)() nothrow pure @property @safe
236 	{
237 		version (assert) if (empty) onRangeError();
238 		alias ET = ContainerElementType!(This, T, true);
239 		return cast(ET) storage[end];
240 	}
241 
242 	/// buffer[i]
243 	auto ref opIndex(this This)(size_t i) nothrow pure @safe
244 	{
245 		version (assert) if (i >= length) onRangeError();
246 		alias ET = ContainerElementType!(This, T, true);
247 		return cast(ET) storage[(start + i) % $];
248 	}
249 
250 	/// buffer[]
251 	Range!This opIndex(this This)() nothrow pure @safe @nogc
252 	{
253 		if (empty)
254 			return typeof(return)(storage[0 .. 0], storage[0 .. 0]);
255 		if (start <= end)
256 			return typeof(return)(storage[start .. end + 1], storage[0 .. 0]);
257 		return typeof(return)(storage[start .. $], storage[0 .. end + 1]);
258 	}
259 
260 	/// buffer[i .. j]
261 	size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc
262 	{
263 		return [i, j];
264 	}
265 
266 	/// ditto
267 	Range!This opIndex(this This)(size_t[2] indices) nothrow pure @safe
268 	{
269 		size_t i = indices[0], j = indices[1];
270 		version (assert)
271 		{
272 			if (i > j) onRangeError();
273 			if (j > length) onRangeError();
274 		}
275 		if (i == j)
276 			return typeof(return)(storage[0 .. 0], storage[0 .. 0]);
277 		i = (start + i) % capacity;
278 		j = (start + j) % capacity;
279 		if (i < j)
280 			return typeof(return)(storage[i .. j], storage[0 .. 0]);
281 		return typeof(return)(storage[i .. $], storage[0 .. j]);
282 	}
283 
284 	static struct Range(ThisT)
285 	{
286 		private
287 		{
288 			static if (is(ThisT == immutable))
289 			{
290 				alias SliceT = immutable(ContainerStorageType!T)[];
291 			}
292 			else static if (is(ThisT == const))
293 			{
294 				alias SliceT = const(ContainerStorageType!T)[];
295 			}
296 			else
297 			{
298 				alias SliceT = ContainerStorageType!T[];
299 			}
300 		}
301 
302 		@disable this();
303 
304 		this(SliceT a, SliceT b) nothrow pure @safe @nogc
305 		{
306 			head = a;
307 			tail = b;
308 		}
309 
310 		This save(this This)() nothrow pure @property @safe @nogc
311 		{
312 			return this;
313 		}
314 
315 		bool empty() const nothrow pure @property @safe @nogc
316 		{
317 			return head.empty && tail.empty;
318 		}
319 
320 		size_t length() const nothrow pure @property @safe @nogc
321 		{
322 			return head.length + tail.length;
323 		}
324 
325 		alias opDollar = length;
326 
327 		auto ref front(this This)() nothrow pure @property @safe
328 		{
329 			if (!head.empty)
330 				return cast(ET) head.front;
331 			return cast(ET) tail.front;
332 		}
333 
334 		auto ref back(this This)() nothrow pure @property @safe
335 		{
336 			if (!tail.empty)
337 				return cast(ET) tail.back;
338 			return cast(ET) head.back;
339 		}
340 
341 		void popFront() nothrow pure @safe
342 		{
343 			if (head.empty)
344 			{
345 				import std.algorithm.mutation : swap;
346 				//Always try to keep `head` non-empty.
347 				swap(head, tail);
348 			}
349 			head.popFront();
350 		}
351 
352 		void popBack() nothrow pure @safe
353 		{
354 			if (!tail.empty)
355 				tail.popBack();
356 			else
357 				head.popBack();
358 		}
359 
360 		/// range[i]
361 		auto ref opIndex(this This)(size_t i) nothrow pure @safe
362 		{
363 			return cast(ET) (i < head.length ? head[i] : tail[i - head.length]);
364 		}
365 
366 		/// range[]
367 		This opIndex(this This)() nothrow pure @safe @nogc
368 		{
369 			return this.save;
370 		}
371 
372 		/// range[i .. j]
373 		size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc
374 		{
375 			return [i, j];
376 		}
377 
378 		/// ditto
379 		This opIndex(this This)(size_t[2] indices) nothrow pure @safe
380 		{
381 			size_t i = indices[0], j = indices[1];
382 			version (assert)
383 			{
384 				if (i > j) onRangeError();
385 				if (j > length) onRangeError();
386 			}
387 			if (i >= head.length)
388 				return typeof(return)(tail[i - head.length .. j - head.length], tail[0 .. 0]);
389 			if (j <= head.length)
390 				return typeof(return)(head[i .. j], head[0 .. 0]);
391 			return typeof(return)(head[i .. $], tail[0 .. j - head.length]);
392 		}
393 
394 		/// range[...]++
395 		auto ref opUnary(string op)() nothrow pure @safe @nogc
396 		if (op == "++" || op == "--")
397 		{
398 			mixin(op ~ "head[];");
399 			mixin(op ~ "tail[];");
400 			return this;
401 		}
402 
403 		/// range[...] = value
404 		auto ref opAssign(U)(const auto ref U value) nothrow pure @safe @nogc
405 		{
406 			head[] = value;
407 			tail[] = value;
408 			return this;
409 		}
410 
411 		/// range[...] += value
412 		auto ref opOpAssign(string op, U)(const auto ref U value) nothrow pure @safe @nogc
413 		{
414 			mixin("head[] " ~ op ~ "= value;");
415 			mixin("tail[] " ~ op ~ "= value;");
416 			return this;
417 		}
418 
419 	private:
420 
421 		alias ET = ContainerElementType!(ThisT, T);
422 
423 		SliceT head, tail;
424 	}
425 
426 	/// Returns: the number of items in the buffer.
427 	size_t length() const nothrow pure @property @safe @nogc { return _length; }
428 
429 	/// ditto
430 	alias opDollar = length;
431 
432 	/// Returns: maximal number of items the buffer can hold without reallocation.
433 	size_t capacity() const nothrow pure @property @safe @nogc { return storage.length; }
434 
435 	/// Returns: whether or not the CyclicBuffer is empty.
436 	bool empty() const nothrow pure @property @safe @nogc { return length == 0; }
437 
438 private:
439 
440 	import containers.internal.storage_type : ContainerStorageType;
441 	import containers.internal.element_type : ContainerElementType;
442 	import containers.internal.mixins : AllocatorState;
443 
444 	enum bool useGC = supportGC && shouldAddGCRange!T;
445 	mixin AllocatorState!Allocator;
446 	ContainerStorageType!T[] storage;
447 	size_t start, end, _length;
448 }
449 
450 version(emsi_containers_unittest) private
451 {
452 	import std.algorithm.comparison : equal;
453 	import std.experimental.allocator.gc_allocator : GCAllocator;
454 	import std.experimental.allocator.building_blocks.free_list : FreeList;
455 	import std.range : iota, lockstep, StoppingPolicy;
456 
457 	struct S
458 	{
459 		int* a;
460 
461 		~this()
462 		{
463 			(*a)++;
464 		}
465 	}
466 
467 	class C
468 	{
469 		int* a;
470 
471 		this(int* a)
472 		{
473 			this.a = a;
474 		}
475 
476 		~this()
477 		{
478 			(*a)++;
479 		}
480 	}
481 }
482 
483 version(emsi_containers_unittest) unittest
484 {
485 	static void test(int size)
486 	{
487 		{
488 			CyclicBuffer!int b;
489 			assert(b.empty);
490 			foreach (i; 0 .. size)
491 			{
492 				assert(b.length == i);
493 				b.insertBack(i);
494 				assert(b.back == i);
495 			}
496 			assert(b.length == size);
497 			foreach (i; 0 .. size)
498 			{
499 				assert(b.length == size - i);
500 				assert(b.front == i);
501 				b.removeFront();
502 			}
503 			assert(b.empty);
504 		}
505 		{
506 			CyclicBuffer!int b;
507 			foreach (i; 0 .. size)
508 			{
509 				assert(b.length == i);
510 				b.insertFront(i);
511 				assert(b.front == i);
512 			}
513 			assert(b.length == size);
514 			foreach (i; 0 .. size)
515 			{
516 				assert(b.length == size - i);
517 				assert(b.back == i);
518 				b.removeBack();
519 			}
520 			assert(b.empty);
521 		}
522 	}
523 
524 	foreach (size; [1, 2, 3, 4, 5, 7, 8, 9, 512, 520, 0x10000, 0x10001, 0x20000])
525 		test(size);
526 }
527 
528 version(emsi_containers_unittest) unittest
529 {
530 	static void test(int prefix, int suffix, int newSize)
531 	{
532 		CyclicBuffer!int b;
533 		foreach_reverse (i; 0 .. suffix)
534 			b.insertFront(i);
535 		foreach (i; suffix .. suffix + prefix)
536 			b.insertBack(i);
537 		assert(b.length == prefix + suffix);
538 		b.reserve(newSize);
539 		assert(b.length == prefix + suffix);
540 		assert(equal(b[], iota(prefix + suffix)));
541 	}
542 
543 	immutable prefixes = [2,  3,  3, 4, 4];
544 	immutable suffixes = [3,  2,  4, 3, 4];
545 	immutable sizes    = [16, 16, 9, 9, 9];
546 
547 	foreach (a, b, c; lockstep(prefixes, suffixes, sizes, StoppingPolicy.requireSameLength))
548 		test(a, b, c);
549 }
550 
551 version(emsi_containers_unittest) unittest
552 {
553 	int* a = new int;
554 	{
555 		CyclicBuffer!S b;
556 		{
557 			S s = { a };
558 			foreach (i; 0 .. 5)
559 				b.insertBack(s);
560 			assert(*a == 5);
561 			foreach (i; 0 .. 5)
562 				b.insertBack(S(a));
563 			assert(*a == 10);
564 			foreach (i; 0 .. 5)
565 			{
566 				b.removeBack();
567 				b.removeFront();
568 			}
569 			assert(*a == 20);
570 		}
571 		assert(*a == 21);
572 	}
573 	assert(*a == 21);
574 }
575 
576 version(emsi_containers_unittest) unittest
577 {
578 	int* a = new int;
579 	CyclicBuffer!C b;
580 	{
581 		C c = new C(a);
582 		foreach (i; 0 .. 10)
583 			b.insertBack(c);
584 		assert(*a == 0);
585 		foreach (i; 0 .. 5)
586 		{
587 			b.removeBack();
588 			b.removeFront();
589 		}
590 		foreach (i; 0 .. b.capacity)
591 			b.insertFront(null);
592 		assert(*a == 0);
593 	}
594 	string s = "";
595 	foreach (i; 0 .. 1_000)
596 		s = s ~ 'a';
597 	s = "";
598 	import core.memory : GC;
599 	GC.collect();
600 	assert(*a == 0 || *a == 1);
601 }
602 
603 version(emsi_containers_unittest) unittest
604 {
605 	CyclicBuffer!int b;
606 	b.insertFront(10);
607 	assert(b[0] == 10);
608 	b.insertFront(20);
609 	assert(b[0] == 20);
610 	assert(b[1] == 10);
611 	b.insertFront(30);
612 	assert(b[0] == 30);
613 	assert(b[1] == 20);
614 	assert(b[2] == 10);
615 	b.insertBack(5);
616 	assert(b[0] == 30);
617 	assert(b[1] == 20);
618 	assert(b[2] == 10);
619 	assert(b[3] == 5);
620 	b.back = 7;
621 	assert(b[3] == 7);
622 }
623 
624 version(emsi_containers_unittest) unittest
625 {
626 	import std.range : isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange;
627 	CyclicBuffer!int b;
628 	static assert(isInputRange!(typeof(b[])));
629 	static assert(isForwardRange!(typeof(b[])));
630 	static assert(isBidirectionalRange!(typeof(b[])));
631 	static assert(isRandomAccessRange!(typeof(b[])));
632 }
633 
634 version(emsi_containers_unittest) unittest
635 {
636 	CyclicBuffer!int b;
637 	assert(b[].empty);
638 }
639 
640 version(emsi_containers_unittest) unittest
641 {
642 	FreeList!(Mallocator, 0, 64) alloc;
643 	FreeList!(GCAllocator, 0, 64) alloc2;
644 	auto b = CyclicBuffer!(int, typeof(&alloc))(&alloc);
645 	auto b2 = CyclicBuffer!(int, typeof(&alloc2))(&alloc2);
646 	auto b3 = CyclicBuffer!(int, GCAllocator)();
647 }
648 
649 version(emsi_containers_unittest) unittest
650 {
651 	static void testConst(const ref CyclicBuffer!int b, int x)
652 	{
653 		assert(b[0] == x);
654 		assert(b.front == x);
655 		static assert(!__traits(compiles, { ++b[0]; } ));
656 		assert(equal(b[], [x]));
657 	}
658 
659 	CyclicBuffer!int b;
660 	b.insertFront(0);
661 	assert(b.front == 0);
662 	b.front++;
663 	assert(b[0] == 1);
664 	b[0]++;
665 	++b[0];
666 	assert(b.front == 3);
667 	assert(!b.empty);
668 	b[0] *= 2;
669 	assert(b[0] == 6);
670 	testConst(b, 6);
671 	b[]++;
672 	assert(equal(b[], [7]));
673 	b[0] = 5;
674 	assert(b[0] == 5);
675 	assert(b.front == 5);
676 	testConst(b, 5);
677 	assert(b[][0] == 5);
678 }
679 
680 version(emsi_containers_unittest) unittest
681 {
682 	int* a = new int;
683 	{
684 		CyclicBuffer!S b;
685 		foreach (i; 0 .. 5)
686 			b.insertBack(S(a));
687 		assert(*a == 5);
688 	}
689 	assert(*a == 10);
690 	*a = 0;
691 	{
692 		CyclicBuffer!S b;
693 		foreach (i; 0 .. 4)
694 			b.insertBack(S(a));
695 		assert(*a == 4);
696 		b.removeFront();
697 		assert(*a == 5);
698 		b.insertBack(S(a));
699 		assert(*a == 6);
700 	}
701 	assert(*a == 10);
702 }
703 
704 version(emsi_containers_unittest) unittest
705 {
706 	CyclicBuffer!int b;
707 	foreach (i; 0 .. 4)
708 		b.insertBack(i);
709 	b.removeFront();
710 	b.removeFront();
711 	b.insertBack(4);
712 	b.insertBack(5);
713 	assert(equal(b[], [2, 3, 4, 5]));
714 	b.reserve(5);
715 	assert(equal(b[], [2, 3, 4, 5]));
716 }
717 
718 version(emsi_containers_unittest) unittest
719 {
720 	CyclicBuffer!int b;
721 	foreach (i; 0 .. 4)
722 		b.insertBack(i);
723 	b.removeFront();
724 	b.removeFront();
725 	b.removeFront();
726 	b.insertBack(4);
727 	b.insertBack(5);
728 	b.insertBack(6);
729 	assert(equal(b[], [3, 4, 5, 6]));
730 	b.reserve(5);
731 	assert(equal(b[], [3, 4, 5, 6]));
732 }
733 
734 version(emsi_containers_unittest) unittest
735 {
736 	static void test(ref CyclicBuffer!int b)
737 	{
738 		assert(equal(b[], [4, 5, 6, 7, 8, 9, 10, 11]));
739 		assert(b[3 .. 3].empty);
740 		auto slice = b[1 .. 6];
741 		assert(equal(slice, [5, 6, 7, 8, 9]));
742 		slice[3 .. 5] = 0;
743 		assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11]));
744 		slice[0 .. 2] += 1;
745 		assert(equal(b[], [4, 6, 7, 7, 0, 0, 10, 11]));
746 		slice[0 .. 2]--;
747 		assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11]));
748 		auto copy = slice.save;
749 		assert(equal(slice, copy));
750 		assert(equal(slice, copy[]));
751 		assert(slice.back == 0);
752 		slice.popBack();
753 		assert(equal(slice, [5, 6, 7, 0]));
754 		assert(slice.back == 0);
755 		slice.popBack();
756 		assert(equal(slice, [5, 6, 7]));
757 		assert(slice.back == 7);
758 		slice.popBack();
759 		assert(equal(slice, [5, 6]));
760 		assert(equal(copy, [5, 6, 7, 0, 0]));
761 		slice[1] = 10;
762 		assert(-copy[1] == -10);
763 		copy[1] *= 2;
764 		assert(slice[1] == 20);
765 		assert(b[2] == 20);
766 		auto copy2 = copy[0 .. $];
767 		assert(equal(copy, copy2));
768 	}
769 
770 	{
771 		CyclicBuffer!int b;
772 		foreach (i; 4 .. 12)
773 			b.insertBack(i);
774 		test(b);
775 	}
776 	{
777 		CyclicBuffer!int b;
778 		foreach (i; 0 .. 8)
779 			b.insertBack(i);
780 		foreach (i; 0 .. 4)
781 			b.removeFront();
782 		foreach (i; 8 .. 12)
783 			b.insertBack(i);
784 		test(b);
785 	}
786 }
787 
788 version(emsi_containers_unittest) unittest
789 {
790 	CyclicBuffer!int b;
791 	foreach (i; 0 .. 10)
792 		b.insertBack(i);
793 	assert(b.capacity >= 10);
794 	b.reserve(12);
795 	assert(b.capacity >= 12);
796 }
797 
798 version(emsi_containers_unittest) unittest
799 {
800 	CyclicBuffer!int b;
801 	foreach (i; 0 .. 6)
802 		b.insertBack(i);
803 	foreach (i; 6 .. 8)
804 		b.insertFront(i);
805 	assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5]));
806 	b.reserve(b.capacity + 1);
807 	assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5]));
808 }
809 
810 version(emsi_containers_unittest) unittest
811 {
812     static class Foo
813     {
814         string name;
815     }
816 
817     CyclicBuffer!Foo b;
818 }