The OpenD Programming Language

1 /**
2  * Dynamic Array
3  * Copyright: © 2015 Economic Modeling Specialists, Intl.
4  * Authors: Brian Schott
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6  */
7 
8 module containers.dynamicarray;
9 
10 private import core.lifetime : move, moveEmplace, copyEmplace, emplace;
11 private import std.traits : isCopyable;
12 private import containers.internal.node : shouldAddGCRange;
13 private import std.experimental.allocator.mallocator : Mallocator;
14 
15 /**
16  * Array that is able to grow itself when items are appended to it. Uses
17  * malloc/free/realloc to manage its storage.
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
23  *         GC-allocated memory.
24  */
25 struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T)
26 {
27 	this(this) @disable;
28 
29 	private import std.experimental.allocator.common : stateSize;
30 
31 	static if (is(typeof((T[] a, const T[] b) => a[0 .. b.length] = b[0 .. $])))
32 	{
33 		/// Either `const(T)` or `T`.
34 		alias AppendT = const(T);
35 
36 		/// Either `const(typeof(this))` or `typeof(this)`.
37 		alias AppendTypeOfThis = const(typeof(this));
38 	}
39 	else
40 	{
41 		alias AppendT = T;
42 		alias AppendTypeOfThis = typeof(this);
43 	}
44 
45 	static if (stateSize!Allocator != 0)
46 	{
47 		/// No default construction if an allocator must be provided.
48 		this() @disable;
49 
50 		/**
51 		 * Use the given `allocator` for allocations.
52 		 */
53 		this(Allocator allocator)
54 		in
55 		{
56 			static if (is(typeof(allocator is null)))
57 				assert(allocator !is null, "Allocator must not be null");
58 		}
59 		do
60 		{
61 			this.allocator = allocator;
62 		}
63 	}
64 
65 	~this()
66 	{
67 		import std.experimental.allocator.mallocator : Mallocator;
68 		import containers.internal.node : shouldAddGCRange;
69 
70 		if (arr is null)
71 			return;
72 
73 		static if ((is(T == struct) || is(T == union))
74 			&& __traits(hasMember, T, "__xdtor"))
75 		{
76 			foreach (ref item; arr[0 .. l])
77 			{
78 				item.__xdtor();
79 			}
80 		}
81 		static if (useGC)
82 		{
83 			import core.memory : GC;
84 			GC.removeRange(arr.ptr);
85 		}
86 		allocator.deallocate(arr);
87 	}
88 
89 	/// Slice operator overload
90 	pragma(inline, true)
91 	auto opSlice(this This)() @nogc
92 	{
93 		return opSlice!(This)(0, l);
94 	}
95 
96 	/// ditto
97 	pragma(inline, true)
98 	auto opSlice(this This)(size_t a, size_t b) @nogc
99 	{
100 		alias ET = ContainerElementType!(This, T);
101 		return cast(ET[]) arr[a .. b];
102 	}
103 
104 	/// Index operator overload
105 	pragma(inline, true)
106 	ref auto opIndex(this This)(size_t i) @nogc
107 	{
108 		return opSlice!(This)(i, i + 1)[0];
109 	}
110 
111 	/**
112 	 * Inserts the given value into the end of the array.
113 	 */
114 	void insertBack(T value)
115 	{
116 		import std.experimental.allocator.mallocator : Mallocator;
117 		import containers.internal.node : shouldAddGCRange;
118 
119 		if (arr.length == 0)
120 		{
121 			arr = cast(typeof(arr)) allocator.allocate(T.sizeof * 4);
122 			static if (useGC)
123 			{
124 				import core.memory: GC;
125 				GC.addRange(arr.ptr, arr.length * T.sizeof);
126 			}
127 		}
128 		else if (l >= arr.length)
129 		{
130 			immutable size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1;
131 			static if (useGC)
132 				void* oldPtr = arr.ptr;
133 			void[] a = cast(void[]) arr;
134 			import std.experimental.allocator.common : reallocate;
135 			allocator.reallocate(a, c * T.sizeof);
136 			arr = cast(typeof(arr)) a;
137 			static if (useGC)
138 			{
139 				import core.memory: GC;
140 				GC.removeRange(oldPtr);
141 				GC.addRange(arr.ptr, arr.length * T.sizeof);
142 			}
143 		}
144 		moveEmplace(*cast(ContainerStorageType!T*)&value, arr[l++]);
145 	}
146 
147 	/// ditto
148 	alias insert = insertBack;
149 
150 	/// ditto
151 	alias insertAnywhere = insertBack;
152 
153 	/// ditto
154 	alias put = insertBack;
155 
156 	/**
157 	 * ~= operator overload
158 	 */
159 	scope ref typeof(this) opOpAssign(string op)(T value) if (op == "~")
160 	{
161 		insert(value);
162 		return this;
163 	}
164 
165 	/**
166 	* ~= operator overload for an array of items
167 	*/
168 	scope ref typeof(this) opOpAssign(string op, bool checkForOverlap = true)(AppendT[] rhs)
169 		if (op == "~" && !is(T == AppendT[]))
170 	{
171 		// Disabling checkForOverlap when this function is called from opBinary!"~"
172 		// is not just for efficiency, but to avoid circular function calls that
173 		// would prevent inference of @nogc, etc.
174 		static if (checkForOverlap)
175 		if ((() @trusted => arr.ptr <= rhs.ptr && arr.ptr + arr.length > rhs.ptr)())
176 		{
177 			// Special case where rhs is a slice of this array.
178 			this = this ~ rhs;
179 			return this;
180 		}
181 		reserve(l + rhs.length);
182 		import std.traits: hasElaborateAssign, hasElaborateDestructor;
183 		static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T))
184 		{
185 			foreach (ref value; rhs)
186 				copyEmplace(value, arr[l++]);
187 		}
188 		else
189 		{
190 			arr[l .. l + rhs.length] = rhs[0 .. rhs.length];
191 			l += rhs.length;
192 		}
193 		return this;
194 	}
195 
196 	/// ditto
197 	scope ref typeof(this) opOpAssign(string op)(ref AppendTypeOfThis rhs)
198 		if (op == "~")
199 	{
200 		return this ~= rhs.arr[0 .. rhs.l];
201 	}
202 
203 	/**
204 	 * ~ operator overload
205 	 */
206 	typeof(this) opBinary(string op)(ref AppendTypeOfThis other) if (op == "~")
207 	{
208 		typeof(this) ret;
209 		ret.reserve(l + other.l);
210 		ret.opOpAssign!("~", false)(arr[0 .. l]);
211 		ret.opOpAssign!("~", false)(other.arr[0 .. other.l]);
212 		return ret;
213 	}
214 
215 	/// ditto
216 	typeof(this) opBinary(string op)(AppendT[] values) if (op == "~")
217 	{
218 		typeof(this) ret;
219 		ret.reserve(l + values.length);
220 		ret.opOpAssign!("~", false)(arr[0 .. l]);
221 		ret.opOpAssign!("~", false)(values);
222 		return ret;
223 	}
224 
225 	/**
226 	 * Ensures sufficient capacity to accommodate `n` elements.
227 	 */
228 	void reserve(size_t n)
229 	{
230 		if (arr.length >= n)
231 			return;
232 		if (arr.ptr is null)
233 		{
234 			size_t c = 4;
235 			if (c < n)
236 				c = n;
237 			arr = cast(typeof(arr)) allocator.allocate(T.sizeof * c);
238 			static if (useGC)
239 			{
240 				import core.memory: GC;
241 				GC.addRange(arr.ptr, arr.length * T.sizeof);
242 			}
243 		}
244 		else
245 		{
246 			size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1;
247 			if (c < n)
248 				c = n;
249 			static if (useGC)
250 				void* oldPtr = arr.ptr;
251 			void[] a = cast(void[]) arr;
252 			import std.experimental.allocator.common : reallocate;
253 			allocator.reallocate(a, c * T.sizeof);
254 			arr = cast(typeof(arr)) a;
255 			static if (useGC)
256 			{
257 				import core.memory: GC;
258 				GC.removeRange(oldPtr);
259 				GC.addRange(arr.ptr, arr.length * T.sizeof);
260 			}
261 		}
262 	}
263 
264 	/**
265 	 * Change the array length.
266 	 * When growing, initialize new elements to the default value.
267 	 */
268 	static if (is(typeof({static T value;}))) // default construction is allowed
269 	void resize(size_t n)
270 	{
271 		import std.traits: hasElaborateAssign, hasElaborateDestructor;
272 		auto toFill = resizeStorage(n);
273 		static if (is(T == struct) && hasElaborateDestructor!T)
274 		{
275 			foreach (ref target; toFill)
276 				emplace(&target);
277 		}
278 		else
279 			toFill[] = T.init;
280 	}
281 
282 	/**
283 	 * Change the array length.
284 	 * When growing, initialize new elements to the given value.
285 	 */
286 	static if (isCopyable!T)
287 	void resize(size_t n, T value)
288 	{
289 		import std.traits: hasElaborateAssign, hasElaborateDestructor;
290 		auto toFill = resizeStorage(n);
291 		static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T))
292 		{
293 			foreach (ref target; toFill)
294 				copyEmplace(value, target);
295 		}
296 		else
297 			toFill[] = value;
298 	}
299 
300 	// Resizes storage only, and returns slice of new memory to fill.
301 	private ContainerStorageType!T[] resizeStorage(size_t n)
302 	{
303 		ContainerStorageType!T[] toFill = null;
304 
305 		if (arr.length < n)
306 			reserve(n);
307 
308 		if (l < n) // Growing?
309 		{
310 			toFill = arr[l..n];
311 		}
312 		else
313 		{
314 			static if ((is(T == struct) || is(T == union))
315 				&& __traits(hasMember, T, "__xdtor"))
316 			{
317 				foreach (i; n..l)
318 					arr[i].__xdtor();
319 			}
320 		}
321 
322 		l = n;
323 		return toFill;
324 	}
325 
326 	/**
327 	 * Remove the item at the given index from the array.
328 	 */
329 	void remove(const size_t i)
330 	{
331 		if (i < this.l)
332 		{
333 			auto next = i + 1;
334 			while (next < this.l)
335 			{
336 				move(arr[next], arr[next - 1]);
337 				++next;
338 			}
339 
340 			--l;
341 			static if ((is(T == struct) || is(T == union))
342 				&& __traits(hasMember, T, "__xdtor"))
343 			{
344 				arr[l].__xdtor();
345 			}
346 		}
347 		else
348 		{
349 			import core.exception : RangeError;
350 			throw new RangeError("Out of range index used to remove element");
351 		}
352 	}
353 
354 	/**
355 	 * Removes the last element from the array.
356 	 */
357 	void removeBack()
358 	{
359 		this.remove(this.length - 1);
360 	}
361 
362 	/// Index assignment support
363 	void opIndexAssign(T value, size_t i) @nogc
364 	{
365 		arr[i] = move(*cast(ContainerStorageType!T*)&value);
366 	}
367 
368 	/// Slice assignment support
369 	static if (isCopyable!T)
370 	void opSliceAssign(T value) @nogc
371 	{
372 		arr[0 .. l] = value;
373 	}
374 
375 	/// ditto
376 	static if (isCopyable!T)
377 	void opSliceAssign(T value, size_t i, size_t j) @nogc
378 	{
379 		arr[i .. j] = value;
380 	}
381 
382 	/// ditto
383 	static if (isCopyable!T)
384 	void opSliceAssign(T[] values) @nogc
385 	{
386 		arr[0 .. l] = values[];
387 	}
388 
389 	/// ditto
390 	static if (isCopyable!T)
391 	void opSliceAssign(T[] values, size_t i, size_t j) @nogc
392 	{
393 		arr[i .. j] = values[];
394 	}
395 
396 	/// Returns: the number of items in the array
397 	size_t length() const nothrow pure @property @safe @nogc { return l; }
398 
399 	/// Ditto
400 	alias opDollar = length;
401 
402 	/// Returns: whether or not the DynamicArray is empty.
403 	bool empty() const nothrow pure @property @safe @nogc { return l == 0; }
404 
405 	/**
406 	 * Returns: a slice to the underlying array.
407 	 *
408 	 * As the memory of the array may be freed, access to this array is
409 	 * highly unsafe.
410 	 */
411 	auto ptr(this This)() @nogc @property
412 	{
413 		alias ET = ContainerElementType!(This, T);
414 		return cast(ET*) arr.ptr;
415 	}
416 
417 	/// Returns: the front element of the DynamicArray.
418 	auto ref T front() pure @property
419 	{
420 		return arr[0];
421 	}
422 
423 	/// Returns: the back element of the DynamicArray.
424 	auto ref T back() pure @property
425 	{
426 		return arr[l - 1];
427 	}
428 
429 private:
430 
431 	import containers.internal.storage_type : ContainerStorageType;
432 	import containers.internal.element_type : ContainerElementType;
433 	import containers.internal.mixins : AllocatorState;
434 
435 	enum bool useGC = supportGC && shouldAddGCRange!T;
436 	mixin AllocatorState!Allocator;
437 	ContainerStorageType!(T)[] arr;
438 	size_t l;
439 }
440 
441 version(emsi_containers_unittest) unittest
442 {
443 	import std.algorithm : equal;
444 	import std.range : iota;
445 	DynamicArray!int ints;
446 	assert(ints.empty);
447 	foreach (i; 0 .. 100)
448 	{
449 		ints.insert(i);
450 		assert(ints.front == 0);
451 		assert(ints.back == i);
452 	}
453 
454 	assert (equal(ints[], iota(100)));
455 	assert (ints.length == 100);
456 	ints[0] = 100;
457 	assert (ints[0] == 100);
458 	ints[0 .. 5] = 20;
459 	foreach (i; ints[0 .. 5])
460 		assert (i == 20);
461 	ints[] = 432;
462 	foreach (i; ints[])
463 		assert (i == 432);
464 
465 	auto arr = ints.ptr;
466 	arr[0] = 1337;
467 	assert(arr[0] == 1337);
468 	assert(ints[0] == 1337);
469 }
470 
471 version(emsi_containers_unittest)
472 {
473 	class Cls
474 	{
475 		int* a;
476 
477 		this(int* a)
478 		{
479 			this.a = a;
480 		}
481 
482 		~this()
483 		{
484 			++(*a);
485 		}
486 	}
487 }
488 
489 version(emsi_containers_unittest) unittest
490 {
491 	int* a = new int;
492 	{
493 		DynamicArray!(Cls) arr;
494 		arr.insert(new Cls(a));
495 	}
496 	assert(*a == 0); // Destructor not called.
497 }
498 
499 version(emsi_containers_unittest) unittest
500 {
501 	import std.exception : assertThrown;
502 	import core.exception : RangeError;
503 	DynamicArray!int empty;
504 	assertThrown!RangeError(empty.remove(1337));
505 	assert(empty.length == 0);
506 
507 	DynamicArray!int one;
508 	one.insert(0);
509 	assert(one.length == 1);
510 	assertThrown!RangeError(one.remove(1337));
511 	assert(one.length == 1);
512 	one.remove(0);
513 	assert(one.length == 0);
514 
515 	DynamicArray!int two;
516 	two.insert(0);
517 	two.insert(1);
518 	assert(two.length == 2);
519 	assertThrown!RangeError(two.remove(1337));
520 	assert(two.length == 2);
521 	two.remove(0);
522 	assert(two.length == 1);
523 	assert(two[0] == 1);
524 	two.remove(0);
525 	assert(two.length == 0);
526 
527 	two.insert(0);
528 	two.insert(1);
529 	assert(two.length == 2);
530 
531 	two.remove(1);
532 	assert(two.length == 1);
533 	assert(two[0] == 0);
534 	assertThrown!RangeError(two.remove(1));
535 	assert(two.length == 1);
536 	assert(two[0] == 0);
537 	two.remove(0);
538 	assert(two.length == 0);
539 }
540 
541 version(emsi_containers_unittest) unittest
542 {
543 	int* a = new int;
544 	DynamicArray!(Cls, Mallocator, true) arr;
545 	arr.insert(new Cls(a));
546 
547 	arr.remove(0);
548 	assert(*a == 0); // Destructor not called.
549 }
550 
551 version(emsi_containers_unittest) unittest
552 {
553 	DynamicArray!(int*, Mallocator, true) arr;
554 
555 	foreach (i; 0 .. 4)
556 		arr.insert(new int(i));
557 
558 	assert (arr.length == 4);
559 
560 	int*[] slice = arr[1 .. $ - 1];
561 	assert (slice.length == 2);
562 	assert (*slice[0] == 1);
563 	assert (*slice[1] == 2);
564 }
565 
566 version(emsi_containers_unittest) unittest
567 {
568 	import std.format : format;
569 
570 	DynamicArray!int arr;
571 	foreach (int i; 0 .. 10)
572 		arr ~= i;
573 	assert(arr.length == 10, "arr.length = %d".format(arr.length));
574 
575 	auto arr2 = arr ~ arr;
576 	assert(arr2.length == 20);
577 	auto arr3 = arr2 ~ [100, 99, 98];
578 	assert(arr3.length == 23);
579 
580 	while(!arr3.empty)
581 		arr3.removeBack();
582 	assert(arr3.empty);
583 
584 }
585 
586 version(emsi_containers_unittest) @system unittest
587 {
588 	DynamicArray!int a;
589 	a.reserve(1000);
590 	assert(a.length == 0);
591 	assert(a.empty);
592 	assert(a.arr.length >= 1000);
593 	int* p = a[].ptr;
594 	foreach (i; 0 .. 1000)
595 	{
596 		a.insert(i);
597 	}
598 	assert(p is a[].ptr);
599 }
600 
601 version(emsi_containers_unittest) unittest
602 {
603 	// Ensure that Array.insert doesn't call the destructor for
604 	// a struct whose state is uninitialized memory.
605 	static struct S
606 	{
607 		int* a;
608 		~this() @nogc nothrow
609 		{
610 			if (a !is null)
611 				++(*a);
612 		}
613 	}
614 	int* a = new int;
615 	{
616 		DynamicArray!S arr;
617 		// This next line may segfault if destructors are called
618 		// on structs in invalid states.
619 		arr.insert(S(a));
620 	}
621 	assert(*a == 1);
622 }
623 
624 version(emsi_containers_unittest) @nogc unittest
625 {
626 	struct HStorage
627 	{
628 		import containers.dynamicarray: DynamicArray;
629 		DynamicArray!int storage;
630 	}
631 	auto hs = HStorage();
632 }
633 
634 version(emsi_containers_unittest) @nogc unittest
635 {
636 	DynamicArray!char a;
637 	const DynamicArray!char b = a ~ "def";
638 	a ~= "abc";
639 	a ~= b;
640 	assert(a[] == "abcdef");
641 	a ~= a;
642 	assert(a[] == "abcdefabcdef");
643 }
644 
645 version(emsi_containers_unittest) unittest
646 {
647 	enum initialValue = 0x69FF5705DAD1AB6CUL;
648 	enum payloadValue = 0x495343303356D18CUL;
649 
650 	static struct S
651 	{
652 		ulong value = initialValue;
653 		@nogc:
654 		@disable this();
655 		this(ulong value) { this.value = value; }
656 		~this() { assert(value == initialValue || value == payloadValue); }
657 	}
658 
659 	auto s = S(payloadValue);
660 
661 	DynamicArray!S arr;
662 	arr.insertBack(s);
663 	arr ~= [s];
664 }
665 
666 version(emsi_containers_unittest) @nogc unittest
667 {
668 	DynamicArray!int a;
669 	a.resize(5, 42);
670 	assert(a.length == 5);
671 	assert(a[2] == 42);
672 	a.resize(3, 17);
673 	assert(a.length == 3);
674 	assert(a[2] == 42);
675 
676 	struct Counter
677 	{
678 		@nogc:
679 		static int count;
680 		@disable this();
681 		this(int) { count++; }
682 		this(this) { count++; }
683 		~this() { count--; }
684 	}
685 
686 	DynamicArray!Counter b;
687 	assert(Counter.count == 0);
688 	static assert(!is(typeof(b.resize(5))));
689 	b.resize(5, Counter(0));
690 	assert(Counter.count == 5);
691 	b.resize(3, Counter(0));
692 	assert(Counter.count == 3);
693 }
694 
695 version(emsi_containers_unittest) @nogc unittest
696 {
697 	struct S { int i = 42; @disable this(this); }
698 	DynamicArray!S a;
699 	a.resize(1);
700 	assert(a[0].i == 42);
701 }
702 
703 version(emsi_containers_unittest) unittest
704 {
705 	import std.experimental.allocator.building_blocks.region : Region;
706 	auto region = Region!Mallocator(1024);
707 
708 	auto arr = DynamicArray!(int, Region!(Mallocator)*, true)(&region);
709 	// reserve and insert back call the common form of reallocate
710 	arr.reserve(10);
711 	arr.insertBack(1);
712 	assert(arr[0] == 1);
713 }
714 
715 version(emsi_containers_unittest) unittest
716 {
717 	auto arr = DynamicArray!int();
718 	arr.resize(5);
719 	arr[] = [1, 2, 3, 4, 5];
720 	arr[1 .. 4] = [12, 13, 14];
721 	assert(arr[] == [1, 12, 13, 14, 5]);
722 }
723 
724 version(emsi_containers_unittest) unittest
725 {
726 	import std.experimental.allocator : RCIAllocator;
727 	auto a = DynamicArray!(int, RCIAllocator)(RCIAllocator.init);
728 }