1 module nxt.container.dynamic_array;
2 
3 import std.experimental.allocator.gc_allocator : GCAllocator;
4 import nxt.allocator_traits : isAllocator;
5 import nxt.my_mallocator : Mallocator;
6 
7 /** Array type with deterministic control of memory. The memory allocated for
8     the array is reclaimed as soon as possible; there is no reliance on the
9     garbage collector.
10 
11     A null `Allocator` means to qcmeman functions. TODO use `Mallocator` by default.
12 
13 	TODO: Check if using the std::vector-compatible store is faster:
14     struct Store {
15         T* begin;
16         T* endData;
17         T* endCapacity;
18 	}
19 
20 	TODO: Use Allocator in place of import nxt.qcmeman : malloc, realloc, free,
21 	gc_addRange, gc_removeRange;
22 
23 	TODO: Replace `withCapacity` with `void capacity(size_t)` like D arrays.
24 
25     TODO: Generalize to bucket array either via specialized allocator to by extra
26     Storage class given as template type parameter. Integrate `nxt.bucket_array` for
27     details.
28 
29     TODO: Use `std.bitmanip.BitArray` for array container storing boolean
30     values.
31 
32     TODO: Add OutputRange.writer support as
33     https://github.com/burner/StringBuffer/blob/master/source/stringbuffer.d#L45
34 
35     TODO: Use `std.traits.areCopyCompatibleArrays`
36 
37     See also https://github.com/izabera/s
38  */
39 @safe struct DynamicArray(T, Allocator = Mallocator, Capacity = size_t)
40 if (!is(immutable T == immutable bool) &&	// TODO: use `BitArray` instead
41     (is(Capacity == ulong) ||	// 3 64-bit words
42      is(Capacity == uint)) &&	// 2 64-bit words
43     isAllocator!Allocator)
44 {
45 	import core.internal.traits : Unqual;
46 
47     /** Growth factor P/Q.
48         https://github.com/facebook/folly/blob/master/folly/docs/FBVector.md#memory-handling
49         Use 1.5 like Facebook's `fbvector` does.
50     */
51     enum _growthP = 3;          // numerator
52     /// ditto
53     enum _growthQ = 2;          // denominator
54 
55     // import core.exception : onOutOfMemoryError;
56     import core.stdc.string : memset;
57     import core.internal.traits : hasElaborateDestructor;
58     import std.experimental.allocator : makeArray;
59     import std.range.primitives : isInputRange, ElementType, hasLength, hasSlicing, isInfinite;
60     import std.traits : hasIndirections, hasAliasing,
61         isMutable, TemplateOf, isArray, isAssignable, isType, hasFunctionAttributes, isIterable, isPointer;
62     import core.lifetime : emplace, move, moveEmplace;
63 
64     import nxt.qcmeman : malloc, realloc, free, gc_addRange, gc_removeRange;
65     import nxt.container.traits : mustAddGCRange;
66 
67     /// Mutable element type.
68     private alias MT = Unqual!T;
69 
70     /// Is `true` if `U` can be assigned to the elements of `this`.
71     enum isElementAssignable(U) = isAssignable!(MT, U);
72 
73     /// Returns: an array of length `initialLength` with all elements default-initialized to `ElementType.init`.
74     static typeof(this) withLength()(in size_t initialLength) /* template-lazy */
75     	=> withCapacityLengthZero(initialLength, initialLength, true);
76 
77     /// Returns: an array with initial capacity `initialCapacity`.
78     static typeof(this) withCapacity()(in size_t initialCapacity) /* template-lazy */
79     	=> withCapacityLengthZero(initialCapacity, 0, false);
80 
81     static if (__traits(isCopyable, T))
82     {
83         /** Construct using
84          * - initial length `length`,
85          * - and value of all elements `elementValue`.
86          */
87         static typeof(this) withLengthElementValue()(in size_t length, T elementValue)
88         in(length <= Capacity.max)
89         {
90             version(D_Coverage) {} else pragma(inline, true);
91             return typeof(return)(Store(typeof(this).allocateWithValue(length, move(elementValue)),
92                                         cast(Capacity)length,
93                                         cast(Capacity)length));
94         }
95     }
96 
97     /** Construct using
98         - initial capacity `capacity`,
99         - initial length `length`,
100         - and default-flag `initFlag`.
101     */
102     private static typeof(this) withCapacityLengthZero()(in size_t capacity, in size_t length, in bool initFlag) @trusted /* template-lazy */
103     in(capacity >= length)
104     in(capacity <= Capacity.max)
105 		=> typeof(return)(Store(typeof(this).allocate(capacity, initFlag),
106 								cast(Capacity)capacity,
107 								cast(Capacity)length));
108 
109     /** Emplace `thatPtr` with elements moved from `elements`. */
110     static ref typeof(this) emplaceWithMovedElements()(typeof(this)* thatPtr, scope T[] elements) @system /* template-lazy */
111     {
112         immutable length = elements.length;
113         thatPtr._store.ptr = typeof(this).allocate(length, false);
114         thatPtr._store.capacity = cast(Capacity)length;
115         thatPtr._store.length = cast(Capacity)length;
116         foreach (immutable i, ref e; elements)
117             moveEmplace(e, thatPtr._mptr[i]);
118         return *thatPtr;
119     }
120 
121     /** Emplace `thatPtr` with elements copied from `elements`. */
122     static ref typeof(this) emplaceWithCopiedElements()(typeof(this)* thatPtr, scope const(T)[] elements) @system /* template-lazy */
123     if (__traits(isCopyable, T))
124     {
125         immutable length = elements.length;
126         thatPtr._store.ptr = typeof(this).allocate(length, false);
127         thatPtr._store.capacity = cast(Capacity)length;
128         thatPtr._store.length = cast(Capacity)length;
129         foreach (immutable i, ref e; elements)
130             thatPtr._mptr[i] = cast(T)e; // TODO: restrict this function using a
131                                          // T-trait where this cast can be @trusted
132         return *thatPtr;
133     }
134 
135     private this(Store store)
136     {
137         _store = store;
138     }
139 
140     /// Construct from uncopyable element `value`.
141     this()(T value) @trusted    /* template-lazy */
142     if (!__traits(isCopyable, T))
143     {
144         _store.ptr = typeof(this).allocate(1, false);
145         _store.capacity = 1;
146         _store.length = 1;
147         moveEmplace(value, _mptr[0]); // TODO: remove `moveEmplace` when compiler does it for us
148     }
149 
150     /// Construct from copyable element `value`.
151     this(U)(U value) @trusted
152     if (__traits(isCopyable, U) &&
153         isElementAssignable!U)
154     {
155         _store.ptr = typeof(this).allocate(1, false);
156         _store.capacity = 1;
157         _store.length = 1;
158         emplace(&_mptr[0], value);
159     }
160 
161     static if (__traits(isCopyable, T) &&
162                !is(T == union)) // forbid copying of unions such as `HybridBin` in hashmap.d
163     {
164         static typeof(this) withElements()(scope const T[] elements) @trusted /* template-lazy */
165         {
166             immutable length = elements.length;
167             auto ptr = typeof(this).allocate(length, false);
168 
169             foreach (immutable i, const element; elements)
170                 // TODO: be more strict
171                 // static if (hasIndirections!T)
172                 // {
173                 //     ptr[i] = element;
174                 // }
175                 // else
176                 // {
177                 //     ptr[i] = *cast(MT*)&element;
178                 // }
179                 ptr[i] = *cast(MT*)&element;
180 
181             // ptr[0 .. length] = elements[];
182             return typeof(return)(Store(ptr,
183                                         cast(Capacity)length,
184                                         cast(Capacity)length));
185         }
186 
187         /// Returns: shallow duplicate of `this`.
188         @property DynamicArray!(MT, Allocator, Capacity) dup()() const @trusted /* template-lazy */
189 			=> typeof(this).withElements(this[]);
190     }
191 
192     /// Construct from the element(s) of the dynamic array `values`.
193     this(U)(U[] values) @trusted
194     if (isElementAssignable!(U))
195     {
196         // TODO: use import emplace_all instead
197 
198         _store.ptr = allocate(values.length, false);
199         static if (!is(Capacity == size_t))
200             assert(values.length <= Capacity.max,
201                    "Minimum capacity doesn't fit in capacity type.");
202         _store.capacity = cast(Capacity)values.length;
203 
204         foreach (index; 0 .. values.length)
205             static if (__traits(isPOD, T))
206                 _mptr[index] = values[index];
207             else
208                 move(values[index], _mptr[index]);
209 
210         setLengthChecked(values.length);
211     }
212 
213     /// Construct from the `n` number of element(s) in the static array `values`.
214     this(uint n, U)(U[n] values) @trusted
215     if (isElementAssignable!(U))
216     {
217         // TODO: use import emplace_all instead
218 
219         _store.ptr = allocate(values.length, false);
220         static assert(values.length <= Capacity.max);
221         _store.capacity = cast(Capacity)values.length;
222 
223         static foreach (index; 0 .. values.length)
224             static if (__traits(isPOD, T))
225                 _mptr[index] = values[index];
226             else
227                 move(values[index], _mptr[index]);
228 
229         setLengthChecked(values.length);
230     }
231     /// ditto
232     this(R)(scope R values) @trusted
233     if (// isRefIterable!R &&
234         isElementAssignable!(ElementType!R) &&
235         !isArray!R)
236     {
237         static if (hasLength!R)
238         {
239             reserve(values.length);
240             size_t index = 0;
241             foreach (ref value; values)
242                 _mptr[index++] = value;
243             setLengthChecked(values.length);
244         }
245         else
246             foreach (ref value; values)
247                 insertBack1(value);
248     }
249 
250     /** Is `true` iff the iterable container `C` can be insert to `this`.
251      */
252     private enum isInsertableContainer(C) = (is(C == struct) && // exclude class ranges for aliasing control
253                                              isRefIterable!C && // elements may be non-copyable
254                                              !isInfinite!C &&
255                                              isElementAssignable!(ElementType!C));
256 
257     /// Construct from the elements `values`.
258     static typeof(this) withElementsOfRange_untested(R)(R values) @trusted
259     if (isInsertableContainer!R)
260     {
261         typeof(this) result;
262 
263         static if (hasLength!R)
264             result.reserve(values.length);
265 
266         static if (__traits(isPOD, ElementType!R) &&
267 				   hasLength!R &&
268                    hasSlicing!R)
269         {
270             import std.algorithm.mutation : copy;
271             copy(values[0 .. values.length],
272                  result._mptr[0 .. values.length]); // TODO: better to use foreach instead?
273             result.setLengthChecked(values.length);
274         }
275         else
276         {
277             static if (hasLength!R)
278             {
279                 size_t i = 0;
280                 foreach (ref value; move(values)) // TODO: remove `move` when compiler does it for us
281                     static if (__traits(isPOD, typeof(value)))
282                         result._mptr[i++] = value;
283                     else
284                         moveEmplace(value, result._mptr[i++]);
285                 result.setLengthChecked(values.length);
286             }
287             else
288             {
289                 // import std.algorithm.mutation : moveEmplaceAll;
290                 /* TODO: optimize with `moveEmplaceAll` that does a raw copy and
291                  * zeroing of values */
292                 foreach (ref value; move(values)) // TODO: remove `move` when compiler does it for us
293                     static if (__traits(isPOD, ElementType!R))
294                         result.insertBack(value);
295                     else
296                         result.insertBackMove(value); // steal element
297             }
298         }
299         return result;
300     }
301 
302     /// No default copying.
303     @disable this(this);
304 
305     // TODO: this gives error in insertBack. why?
306     // void opAssign()(typeof(this) rhs) @trusted pure nothrow @nogc /* template-lazy */
307     // {
308     //     move(rhs, this);
309     // }
310 
311     /** Destruct.
312      *
313      * TODO: what effect does have here?
314      * See_Also: https://github.com/atilaneves/automem/blob/master/source/automem/vector.d#L92
315      */
316     ~this() nothrow @nogc /*TODO: scope*/
317     {
318         releaseElementsStore();
319     }
320 
321     /// Empty.
322     void clear() @nogc
323     {
324         releaseElementsStore();
325         resetInternalData();
326     }
327 
328     /// Release elements and internal store.
329     private void releaseElementsStore() @nogc @trusted
330     {
331         foreach (immutable index; 0 .. _store.length)
332             static if (hasElaborateDestructor!T)
333                 .destroy(_mptr[index]);
334             else static if (is(T == class) || isPointer!T || hasIndirections!T)
335                 _mptr[index] = T.init; // nullify any pointers
336         freeStore();
337     }
338 
339     /// Free internal store.
340     private void freeStore() @trusted
341     {
342         static if (mustAddGCRange!T)
343             gc_removeRange(_mptr);
344         free(_mptr);
345     }
346 
347     /// Reset internal data.
348     private void resetInternalData() @nogc
349     {
350         version(D_Coverage) {} else pragma(inline, true);
351         _store.ptr = null;
352         _store.capacity = 0;
353         _store.length = 0;
354     }
355 
356     /** Allocate heap region with `initialCapacity` number of elements of type `T`.
357      *
358      * If `initFlag` is `true` elements will be initialized
359      */
360     private static MT* allocate(in size_t initialCapacity, in bool initFlag) @trusted
361     {
362         immutable size_t numBytes = initialCapacity * T.sizeof;
363 
364         typeof(return) ptr = null;
365         if (initFlag)
366         {
367             static if (__traits(isZeroInit, T) &&
368                        __traits(hasMember, Allocator, "allocateZeroed") &&
369                        is(typeof(allocator.allocateZeroed(numBytes)) == void[]))
370                 ptr = cast(typeof(return))(allocator.allocateZeroed(numBytes).ptr);
371             else
372             {
373                 ptr = cast(typeof(return))(allocator.allocate(numBytes).ptr);
374                 static if (__traits(isZeroInit, T))
375                     memset(ptr, 0, numBytes);
376                 else
377                     foreach (i; 0 .. initialCapacity)
378                         ptr[i] = MT.init;
379             }
380         }
381         else
382             ptr = cast(typeof(return))(allocator.allocate(numBytes).ptr);
383 
384         if (ptr is null &&
385             initialCapacity >= 1 )
386             // TODO: onOutOfMemoryError();
387             return null;
388 
389         static if (mustAddGCRange!T)
390             gc_addRange(ptr, numBytes);
391 
392         return ptr;
393     }
394 
395     static if (__traits(isCopyable, T))
396     {
397         /** Allocate heap region with `initialCapacity` number of elements of type `T` all set to `elementValue`.
398          */
399         private static MT* allocateWithValue(in size_t initialCapacity, T elementValue) @trusted
400         {
401             immutable size_t numBytes = initialCapacity * T.sizeof;
402 
403             typeof(return) ptr = null;
404             ptr = allocator.makeArray!MT(initialCapacity, elementValue).ptr; // TODO: set length
405             if (ptr is null &&
406                 initialCapacity >= 1)
407                 // TODO: onOutOfMemoryError();
408                 return null;
409 
410             static if (mustAddGCRange!T)
411                 gc_addRange(ptr, numBytes);
412 
413             return ptr;
414         }
415     }
416 
417     /** Comparison for equality. */
418     bool opEquals()(const auto ref typeof(this) rhs) const scope /* template-lazy */
419 	{
420 		version(LDC) pragma(inline, true);
421 		return opSlice() == rhs.opSlice();
422 	}
423 
424     /// ditto
425 	pragma(inline, true)
426     bool opEquals(U)(const scope U[] rhs) const scope
427     if (is(typeof(T[].init == U[].init)))
428 		=> opSlice() == rhs;
429 
430     /// Calculate D associative array (AA) key hash.
431 	pragma(inline, true)
432     hash_t toHash()() const scope @trusted /* template-lazy */
433 		=> .hashOf(length) + .hashOf(opSlice());
434 
435     static if (__traits(isCopyable, T))
436     {
437         /** Construct a string representation of `this` at `sink`.
438          */
439         void toString(Sink)(ref scope Sink sink) const scope /* template-lazy */
440         {
441 			import std.conv : to;
442             sink("[");
443             foreach (immutable index, ref value; opSlice())
444             {
445                 sink(to!string(value));
446                 if (index + 1 < length) { sink(", "); } // separator
447             }
448             sink("]");
449         }
450     }
451 
452     /// Check if empty.
453 	pragma(inline, true)
454     bool empty() const @property scope => _store.length == 0;
455 
456     /// Get length.
457 	pragma(inline, true)
458     @property size_t length() const scope => _store.length;	// can't be template-lazy
459     alias opDollar = length;    /// ditto
460 
461     /** Set length to `newLength`.
462      *
463      * If `newLength` < `length` elements are truncate.
464      * If `newLength` > `length` default-initialized elements are appended.
465      */
466     @property void length(in size_t newLength) @trusted scope // can't template-lazy
467     {
468         if (newLength < length) // if truncatation
469         {
470             static if (hasElaborateDestructor!T)
471                 foreach (immutable index; newLength .. _store.length)
472                     .destroy(_mptr[index]);
473             else static if (mustAddGCRange!T)
474                 foreach (immutable index; newLength .. _store.length)
475                     _mptr[index] = T.init; // avoid GC mark-phase dereference
476         }
477         else
478         {
479             reserve(newLength);
480             static if (hasElaborateDestructor!T)
481                 // TODO: remove when compiler does it for us
482                 foreach (immutable index; _store.length .. newLength)
483                 {
484                     // TODO: remove when compiler does it for us:
485                     static if (__traits(isCopyable, T))
486                         emplace(&_mptr[index], T.init);
487                     else
488                     {
489                         auto _ = T.init;
490                         moveEmplace(_, _mptr[index]);
491                     }
492                 }
493             else
494                 _mptr[_store.length .. newLength] = T.init;
495         }
496 
497         setLengthChecked(newLength);
498     }
499 
500     /// Set capacity, checking for overflow when `Capacity` is not `size_t`.
501     private void setLengthChecked(in size_t newLength) scope
502     {
503         static if (!is(Capacity == size_t))
504             assert(newLength <= Capacity.max,
505                    "New length doesn't fit in capacity type.");
506         _store.length = cast(Capacity)newLength;
507     }
508 
509     /// Get capacity.
510 	pragma(inline, true)
511     @property size_t capacity() const scope => _store.capacity; // can't be template-lazy
512 
513     /** Ensures sufficient capacity to accommodate for minimumCapacity number of
514         elements. If `minimumCapacity` < `capacity`, this method does nothing.
515      */
516     size_t reserve(in size_t minimumCapacity) @trusted scope pure nothrow @nogc
517     {
518         static if (!is(Capacity == size_t))
519             assert(minimumCapacity <= Capacity.max,
520                    "Minimum capacity doesn't fit in capacity type.");
521 
522         if (minimumCapacity <= capacity)
523             return capacity;
524 
525         return reallocateAndSetCapacity(_growthP * minimumCapacity / _growthQ);
526         // static if (__VERSION__ >= 2098) import std.math.algebraic : nextPow2; else import std.math : nextPow2;
527         // reallocateAndSetCapacity(minimumCapacity.nextPow2);
528     }
529 
530     /// Reallocate storage.
531     private size_t reallocateAndSetCapacity()(in size_t newCapacity) @trusted /* template-lazy */
532     {
533         static if (!is(Capacity == size_t))
534             assert(newCapacity <= Capacity.max,
535                    "New capacity doesn't fit in capacity type.");
536 
537         static if (mustAddGCRange!T)
538             gc_removeRange(_store.ptr);
539 
540         _store.capacity = cast(Capacity)newCapacity;
541         _store.ptr = cast(T*)realloc(_mptr, T.sizeof * _store.capacity);
542 
543         if (_store.ptr is null &&
544             newCapacity >= 1)
545             // TODO: onOutOfMemoryError();
546             return _store.capacity;
547 
548         static if (mustAddGCRange!T)
549             gc_addRange(_store.ptr, _store.capacity * T.sizeof);
550 
551 		return _store.capacity;
552     }
553 
554     /// Index support.
555 	pragma(inline, true)
556     ref inout(T) opIndex()(in size_t i) inout return scope /* template-lazy */
557 		=> opSlice()[i];
558 
559     /// Slice support.
560 	pragma(inline, true)
561     inout(T)[] opSlice()(in size_t i, in size_t j) inout return scope /* template-lazy */
562 		=> opSlice()[i .. j];
563     /// ditto
564 	pragma(inline, true)
565     inout(T)[] opSlice()() inout return scope @trusted /* template-lazy */
566     	=> _store.ptr[0 .. _store.length];
567 
568     /// Index assignment support.
569     ref T opIndexAssign(U)(scope U value, in size_t i) @trusted return scope
570     {
571         static if (!__traits(isPOD, T))
572         {
573             move(*(cast(MT*)(&value)), _mptr[i]); // TODO: is this correct?
574             return opSlice()[i];
575         }
576         else static if ((is(T == class) || isPointer!T || hasIndirections!T) &&
577                         !isMutable!T)
578             static assert(0, "Cannot modify constant elements with indirections");
579         else
580             return opSlice()[i] = value;
581     }
582 
583     /// Slice assignment support.
584 	pragma(inline, true)
585     T[] opSliceAssign(U)(scope U value) return scope
586     	=> opSlice()[] = value;
587     /// ditto
588 	pragma(inline, true)
589     T[] opSliceAssign(U)(scope U value, in size_t i, in size_t j) return scope
590 		=> opSlice()[i .. j] = value;
591 
592     /// Get reference to front element.
593 	pragma(inline, true)
594     @property ref inout(T) front()() inout return scope /* template-lazy */
595 		=> opSlice()[0];      // range-checked by default
596 
597     /// Get reference to back element.
598 	pragma(inline, true)
599     @property ref inout(T) back()() inout return scope /* template-lazy */
600 		=> opSlice()[_store.length - 1]; // range-checked by default
601 
602     /** Move `value` into the end of the array.
603      */
604     void insertBackMove()(scope ref T value) scope @trusted /* template-lazy */
605     {
606         version(LDC) pragma(inline, true);
607         reserve(_store.length + 1);
608         moveEmplace(value, _mptr[_store.length]);
609         _store.length += 1;
610     }
611 
612     /** Insert `value` into the end of the array.
613      */
614     void insertBack()(scope T value) scope @trusted /* template-lazy */
615     {
616         static if (__traits(isPOD, T))
617         {
618             reserve(_store.length + 1);
619             _mptr[_store.length] = value;
620             _store.length += 1;
621         }
622         else
623             insertBackMove(*cast(MT*)(&value));
624     }
625 
626     alias put = insertBack;
627 
628     /** Insert the elements `values` into the end of the array.
629      */
630     void insertBack(U)(U[] values...) scope @trusted
631     if (isElementAssignable!U &&
632         __traits(isCopyable, U))       // prevent accidental move of l-value `values`
633     {
634         if (values.length == 1) // TODO: branch should be detected at compile-time
635             // twice as fast as array assignment below
636             return insertBack(values[0]);
637 
638         static if (is(T == immutable(T)))
639         {
640             /* An array of immutable values cannot overlap with the `this`
641                mutable array container data, which entails no need to check for
642                overlap.
643             */
644             reserve(_store.length + values.length);
645             _mptr[_store.length .. _store.length + values.length] = values;
646         }
647         else
648         {
649             import nxt.overlapping : overlaps;
650             if (_store.ptr == values.ptr) // called for instances as: `this ~= this`
651             {
652                 reserve(2*_store.length); // invalidates `values.ptr`
653                 foreach (immutable i; 0 .. _store.length)
654                     _mptr[_store.length + i] = _store.ptr[i];
655             }
656             else if (overlaps(this[], values[]))
657                 assert(0, `TODO: Handle overlapping arrays`);
658             else
659             {
660                 reserve(_store.length + values.length);
661                 _mptr[_store.length .. _store.length + values.length] = values;
662             }
663         }
664         _store.length += values.length;
665     }
666 
667     /** Insert the elements `elements` into the end of the array.
668      */
669     void insertBack(R)(scope R elements) @trusted
670     if (isInsertableContainer!R)
671     {
672         import std.range.primitives : hasLength;
673         static if (isInputRange!R &&
674                    hasLength!R)
675         {
676             reserve(_store.length + elements.length);
677             import std.algorithm.mutation : copy;
678             copy(elements, _mptr[_store.length .. _store.length + elements.length]);
679             _store.length += elements.length;
680         }
681         else
682         {
683             foreach (ref element; move(elements)) // TODO: remove `move` when compiler does it for us
684                 static if (__traits(isCopyable, ElementType!R))
685                     insertBack(element);
686                 else
687                     insertBackMove(element);
688         }
689     }
690     /// ditto
691     alias put = insertBack;
692 
693     /** Remove last value fromm the end of the array.
694      */
695     void popBack()() @trusted in(!empty) /* template-lazy */
696     {
697         version(D_Coverage) {} else pragma(inline, true);
698         _store.length -= 1;
699         static if (hasElaborateDestructor!T)
700             .destroy(_mptr[_store.length]);
701         else static if (mustAddGCRange!T)
702             _mptr[_store.length] = T.init; // avoid GC mark-phase dereference
703     }
704 
705     /** Rmove `n` last values from the end of the array.
706 
707         See_Also: http://mir-algorithm.libmir.org/mir_appender.html#.ScopedBuffer.popBackN
708      */
709     void popBackN()(in size_t n) @trusted in(length >= n) /* template-lazy */
710     {
711         _store.length -= n;
712         static if (hasElaborateDestructor!T)
713             foreach (immutable index; 0 .. n)
714                 .destroy(_mptr[_store.length + index]);
715         else static if (mustAddGCRange!T)
716             foreach (immutable index; 0 .. n)
717                 _mptr[_store.length + index] = T.init; // avoid GC mark-phase dereference
718     }
719 
720     /** Pop back element and return it.
721 
722         This is well-missed feature of C++'s `std::vector` because of problems
723         with exception handling. For more details see
724         https://stackoverflow.com/questions/12600330/pop-back-return-value.
725      */
726     T takeBack()() @trusted in(!empty) /* template-lazy */
727     {
728         version(D_Coverage) {} else pragma(inline, true);
729         _store.length -= 1;
730         static if (!__traits(isPOD, T))
731             return move(_mptr[_store.length]);
732         else static if (is(T == class) || isPointer!T || hasIndirections!T) // fast, medium, slow path
733         {
734             T e = void;
735             moveEmplace(_mptr[_store.length], e); // reset any pointers at `back`
736             return e;
737         }
738         else
739             return _mptr[_store.length];
740     }
741 
742     /** Pop element at `index`. */
743     void popAt()(in size_t index) @trusted @("complexity", "O(length)") /* template-lazy */
744     in(index < this.length)
745     {
746         static if (hasElaborateDestructor!T)
747             .destroy(_mptr[index]);
748         else static if (mustAddGCRange!T)
749             _mptr[index] = T.init; // avoid GC mark-phase dereference
750         shiftToFrontAt(index);
751         _store.length -= 1;
752     }
753 
754     /** Move element at `index` to return. */
755     static if (isMutable!T)
756 		T moveAt()(in size_t index) @trusted @("complexity", "O(length)") /* template-lazy */
757 		in(index < this.length)
758 		{
759 			auto value = move(_mptr[index]);
760 			shiftToFrontAt(index);
761 			_store.length -= 1;
762 			return move(value); // TODO: remove `move` when compiler does it for us
763 		}
764 
765     /** Move element at front. */
766     static if (isMutable!T)
767 		pragma(inline, true)
768 		T takeFront()() @("complexity", "O(length)") /* template-lazy */
769     		=> moveAt(0);
770 
771     private void shiftToFrontAt()(in size_t index) @trusted /* template-lazy */
772     {
773         // TODO: use this instead:
774         // immutable si = index + 1;   // source index
775         // immutable ti = index;       // target index
776         // immutable restLength = this.length - (index + 1);
777         // import std.algorithm.mutation : moveEmplaceAll;
778         // moveEmplaceAll(_mptr[si .. si + restLength],
779         //                _mptr[ti .. ti + restLength]);
780         foreach (immutable i; 0 .. this.length - (index + 1)) // each element index that needs to be moved
781         {
782             immutable si = index + i + 1; // source index
783             immutable ti = index + i; // target index
784             moveEmplace(_mptr[si], // TODO: remove `move` when compiler does it for us
785                         _mptr[ti]);
786         }
787     }
788 
789     /** Forwards to $(D insertBack(values)).
790      */
791     void opOpAssign(string op)(T value) if (op == "~")
792     {
793         version(D_Coverage) {} else pragma(inline, true);
794         insertBackMove(value);
795     }
796     /// ditto
797     void opOpAssign(string op, U)(U[] values...) @trusted if (op == "~" &&
798         isElementAssignable!U &&
799         __traits(isCopyable, U))       // prevent accidental move of l-value `values`
800     {
801         version(D_Coverage) {} else pragma(inline, true);
802         insertBack(values);
803     }
804     /// ditto
805     void opOpAssign(string op, R)(R values) if (op == "~" &&
806         isInputRange!R &&
807         !isInfinite!R &&
808         !isArray!R &&
809         isElementAssignable!(ElementType!R))
810     {
811         version(D_Coverage) {} else pragma(inline, true);
812         insertBack(values);
813     }
814 
815     void opOpAssign(string op)(auto ref typeof(this) values) if (op == "~")
816     {
817         version(D_Coverage) {} else pragma(inline, true);
818         insertBack(values[]);
819     }
820 
821     // typeof(this) opBinary(string op, R)(R values) if (op == "~")
822     // {
823     //     // TODO: optimize
824     //     typeof(this) result;
825     //     result ~= this[];
826     //     assert(result.length == length);
827     //     result ~= values[];
828     //     return result;
829     // }
830 
831     /// Unsafe access to pointer.
832 	pragma(inline, true)
833     inout(T)* ptr()() inout return @system /* template-lazy */
834 		=> _store.ptr;
835 
836     /// Mutable pointer.
837 	pragma(inline, true)
838     private MT* _mptr() const return @trusted
839 		=> cast(typeof(return))_store.ptr;
840 
841 private:
842     import nxt.allocator_traits : AllocatorState;
843     mixin AllocatorState!Allocator; // put first as emsi-containers do
844 
845     /** For more convenient construction. */
846     struct Store
847     {
848         static if (hasFunctionAttributes!(Allocator.allocate, "@nogc"))
849         {
850             import nxt.gc_traits : NoGc;
851             @NoGc T* ptr;       // non-GC-allocated
852         }
853         else
854             T* ptr;             // GC-allocated
855         Capacity capacity; // store capacity
856         Capacity length;   // store length
857     }
858 
859     Store _store;
860 }
861 
862 import std.traits : isInstanceOf;
863 import std.functional : unaryFun;
864 
865 /** Remove all elements matching `predicate`.
866 
867     Returns: number of elements that were removed.
868 
869     TODO: implement version that doesn't use a temporary array `tmp`, which is
870     probably faster for small arrays.
871  */
872 size_t remove(alias predicate, C)(ref C c) @trusted @("complexity", "O(length)")
873 if (isInstanceOf!(DynamicArray, C) &&
874     is(typeof(unaryFun!predicate(C.init[0]))))
875 {
876     C tmp;
877     size_t count = 0;
878     foreach (immutable i; 0 .. c.length)
879     {
880         if (unaryFun!predicate(c[i]))
881         {
882             count += 1;
883             import core.internal.traits : hasElaborateDestructor;
884             import nxt.container.traits : mustAddGCRange;
885             alias T = typeof(c[i]);
886             static if (hasElaborateDestructor!(T))
887                 .destroy(c[i]);
888             else static if (mustAddGCRange!(T))
889                 c[i] = T.init;    // avoid GC mark-phase dereference
890         }
891         else
892             tmp.insertBackMove(c[i]); // TODO: remove unnecessary clearing of `_mptr[i]`
893     }
894 
895     c.freeStore();
896 
897     import core.lifetime : moveEmplace;
898     moveEmplace(tmp, c);
899 
900     return count;
901 }
902 
903 /// construct and append from slices
904 @safe pure nothrow @nogc unittest
905 {
906     alias T = int;
907     alias A = DynamicArray!(T, Mallocator, uint);
908     static if (size_t.sizeof == 8) // only 64-bit
909         static assert(A.sizeof == 2 * size_t.sizeof); // only two words
910 
911     auto a = A([10, 11, 12].s);
912 
913     a ~= a[];
914     assert(a[] == [10, 11, 12,
915                    10, 11, 12].s);
916 
917     a ~= false;
918     assert(a[] == [10, 11, 12,
919                    10, 11, 12, 0].s);
920 }
921 
922 ///
923 @safe pure nothrow @nogc unittest
924 {
925     alias T = int;
926     alias A = DynamicArray!(T);
927 
928     A a;
929 
930     a.length = 1;
931     assert(a.length == 1);
932     assert(a.capacity >= 1);
933 
934     a[0] = 10;
935 
936     a.insertBack(11, 12);
937 
938     a ~= T.init;
939     a.insertBack([3].s);
940     assert(a[] == [10, 11, 12, 0, 3].s);
941 
942     import std.algorithm.iteration : filter;
943 
944     a.insertBack([42].s[].filter!(_ => _ is 42));
945     assert(a[] == [10, 11, 12, 0, 3, 42].s);
946 
947     a.insertBack([42].s[].filter!(_ => _ !is 42));
948     assert(a[] == [10, 11, 12, 0, 3, 42].s);
949 
950     a ~= a[];
951     assert(a[] == [10, 11, 12, 0, 3, 42,
952                    10, 11, 12, 0, 3, 42].s);
953 }
954 
955 ///
956 @safe pure nothrow @nogc unittest
957 {
958     alias T = int;
959     alias A = DynamicArray!(T);
960 
961     A a;                        // default construction allowed
962     assert(a.empty);
963     assert(a.length == 0);
964     assert(a.capacity == 0);
965     assert(a[] == []);
966 
967     auto b = DynamicArray!int.withLength(3);
968     assert(!b.empty);
969     assert(b.length == 3);
970     assert(b.capacity == 3);
971     b[0] = 1;
972     b[1] = 2;
973     b[2] = 3;
974     assert(b[] == [1, 2, 3].s);
975 
976     b[] = [4, 5, 6].s;
977     assert(b[] == [4, 5, 6].s);
978 
979     const c = DynamicArray!int.withCapacity(3);
980     assert(c.empty);
981     assert(c.capacity == 3);
982     assert(c[] == []);
983 
984 	static if (hasPreviewDIP1000)
985 		static assert(!__traits(compiles, { T[] f() @safe { A a; return a[]; } }));
986 
987     const e = DynamicArray!int([1, 2, 3, 4].s);
988     assert(e.length == 4);
989     assert(e[] == [1, 2, 3, 4].s);
990 }
991 
992 ///
993 @trusted pure nothrow @nogc unittest
994 {
995     alias T = int;
996     alias A = DynamicArray!(T);
997 
998     auto a = A([1, 2, 3].s);
999     A b = a.dup;                // copy construction enabled
1000 
1001     assert(a[] == b[]);          // same content
1002     assert(&a[0] !is &b[0]); // but not the same
1003 
1004     assert(b[] == [1, 2, 3].s);
1005     assert(b.length == 3);
1006 
1007     b ~= 4;
1008     assert(a != b);
1009     a.clear();
1010     assert(a != b);
1011     b.clear();
1012     assert(a == b);
1013 
1014     const c = A([1, 2, 3].s);
1015 	assert(c.length == 3);
1016 }
1017 
1018 /// DIP-1000 return ref escape analysis
1019 @safe pure nothrow @nogc unittest
1020 {
1021     alias T = int;
1022     alias A = DynamicArray!T;
1023 
1024 	static if (hasPreviewDIP1000)
1025 		static assert(!__traits(compiles, { T[] leakSlice() @safe pure nothrow @nogc { A a; return a[]; } }));
1026 
1027     T* leakPointer() @safe pure nothrow @nogc
1028     {
1029         A a;
1030         return a._store.ptr;    // TODO: shouldn't compile with -dip1000
1031     }
1032 
1033     const _lp = leakPointer();    // TODO: shouldn't compile with -dip1000
1034 }
1035 
1036 version(unittest)
1037 {
1038     private static struct SomeUncopyable
1039     {
1040         @disable this(this);
1041         int _x;
1042     }
1043 }
1044 
1045 /// construct and insert from non-copyable element type passed by value
1046 @safe pure nothrow /*@nogc*/ unittest
1047 {
1048     alias A = DynamicArray!(SomeUncopyable);
1049 
1050     A a = A(SomeUncopyable(17));
1051     assert(a[] == [SomeUncopyable(17)]);
1052 
1053     a.insertBack(SomeUncopyable(18));
1054     assert(a[] == [SomeUncopyable(17),
1055                    SomeUncopyable(18)]);
1056 
1057     a ~= SomeUncopyable(19);
1058     assert(a[] == [SomeUncopyable(17),
1059                    SomeUncopyable(18),
1060                    SomeUncopyable(19)]);
1061 }
1062 
1063 /// construct from slice of uncopyable type
1064 @safe pure nothrow @nogc unittest
1065 {
1066     alias _A = DynamicArray!(SomeUncopyable);
1067     // TODO: can we safely support this?: A a = [SomeUncopyable(17)];
1068 }
1069 
1070 // construct from array with uncopyable elements
1071 @safe pure nothrow @nogc unittest
1072 {
1073     alias A = DynamicArray!(SomeUncopyable);
1074 
1075     const A a;
1076     assert(a.empty);
1077 
1078     // TODO: a.insertBack(A.init);
1079     assert(a.empty);
1080 
1081 	const _ = a.toHash;
1082 }
1083 
1084 // construct from ranges of uncopyable elements
1085 @safe pure nothrow @nogc unittest
1086 {
1087     alias T = SomeUncopyable;
1088     alias A = DynamicArray!T;
1089 
1090     const A a;
1091     assert(a.empty);
1092 
1093     // import std.algorithm.iteration : map, filter;
1094 
1095     // const b = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => T(_^^2))); // hasLength
1096     // assert(b.length == 3);
1097     // assert(b == [T(100), T(400), T(900)].s);
1098 
1099     // const c = A.withElementsOfRange_untested([10, 20, 30].s[].filter!(_ => _ == 30).map!(_ => T(_^^2))); // !hasLength
1100     // assert(c.length == 1);
1101     // assert(c[0].x == 900);
1102 }
1103 
1104 // construct from ranges of copyable elements
1105 @safe pure nothrow @nogc unittest
1106 {
1107     alias T = int;
1108     alias A = DynamicArray!T;
1109 
1110     const A a;
1111     assert(a.empty);
1112 
1113     import std.algorithm.iteration : map, filter;
1114 
1115     const b = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => T(_^^2))); // hasLength
1116     assert(b.length == 3);
1117     assert(b == [T(100), T(400), T(900)].s);
1118 
1119     () @trusted {               // TODO: remove @trusted
1120         const c = A.withElementsOfRange_untested([10, 20, 30].s[].filter!(_ => _ == 30).map!(_ => T(_^^2))); // !hasLength
1121         assert(c == [T(900)].s);
1122     } ();
1123 }
1124 
1125 /// construct with string as element type that needs GC-range
1126 @safe pure nothrow @nogc unittest
1127 {
1128     alias T = string;
1129     alias A = DynamicArray!(T);
1130 
1131     A a;
1132     a ~= `alpha`;
1133     a ~= `beta`;
1134     a ~= [`gamma`, `delta`].s;
1135     assert(a[] == [`alpha`, `beta`, `gamma`, `delta`].s);
1136 
1137     const b = [`epsilon`].s;
1138 
1139     a.insertBack(b);
1140     assert(a[] == [`alpha`, `beta`, `gamma`, `delta`, `epsilon`].s);
1141 
1142     a ~= b;
1143     assert(a[] == [`alpha`, `beta`, `gamma`, `delta`, `epsilon`, `epsilon`].s);
1144 }
1145 
1146 /// convert to string
1147 version(none)                   // TODO: make this work
1148 unittest
1149 {
1150     alias T = int;
1151     alias A = DynamicArray!(T);
1152 
1153     DynamicArray!char sink;
1154     A([1, 2, 3]).toString(sink.put);
1155 }
1156 
1157 /// iteration over mutable elements
1158 @safe pure nothrow @nogc unittest
1159 {
1160     alias T = int;
1161     alias A = DynamicArray!(T);
1162 
1163     auto a = A([1, 2, 3].s);
1164 
1165     foreach (immutable i, const e; a)
1166         assert(i + 1 == e);
1167 }
1168 
1169 /// iteration over `const`ant elements
1170 @safe pure nothrow @nogc unittest
1171 {
1172     alias T = const(int);
1173     alias A = DynamicArray!(T);
1174 
1175     auto a = A([1, 2, 3].s);
1176 
1177     foreach (immutable i, const e; a)
1178         assert(i + 1 == e);
1179 }
1180 
1181 /// iteration over immutable elements
1182 @safe pure nothrow @nogc unittest
1183 {
1184     alias T = immutable(int);
1185     alias A = DynamicArray!(T);
1186 
1187     auto a = A([1, 2, 3].s);
1188 
1189     foreach (immutable i, const e; a)
1190         assert(i + 1 == e);
1191 }
1192 
1193 /// removal
1194 @safe pure nothrow @nogc unittest
1195 {
1196     alias T = int;
1197     alias A = DynamicArray!(T);
1198 
1199     auto a = A([1, 2, 3].s);
1200     assert(a == [1, 2, 3].s);
1201 
1202     assert(a.takeFront() == 1);
1203     assert(a == [2, 3].s);
1204 
1205     a.popAt(1);
1206     assert(a == [2].s);
1207 
1208     a.popAt(0);
1209     assert(a == []);
1210 
1211     a.insertBack(11);
1212     assert(a == [11].s);
1213 
1214     assert(a.takeBack == 11);
1215 
1216     a.insertBack(17);
1217     assert(a == [17].s);
1218     a.popBack();
1219     assert(a.empty);
1220 
1221     a.insertBack([11, 12, 13, 14, 15].s[]);
1222     a.popAt(2);
1223     assert(a == [11, 12, 14, 15].s);
1224     a.popAt(0);
1225     assert(a == [12, 14, 15].s);
1226     a.popAt(2);
1227 
1228     assert(a == [12, 14].s);
1229 
1230     a ~= a;
1231 }
1232 
1233 /// removal
1234 @safe pure nothrow unittest
1235 {
1236     import nxt.container.traits : mustAddGCRange;
1237 
1238     size_t mallocCount = 0;
1239     size_t freeCount = 0;
1240 
1241     struct S
1242     {
1243         @safe pure nothrow @nogc:
1244 
1245         alias E = int;
1246 
1247         import nxt.qcmeman : malloc, free;
1248 
1249         this(E x) @trusted
1250         {
1251             _ptr = cast(E*)malloc(E.sizeof);
1252             mallocCount += 1;
1253             *_ptr = x;
1254         }
1255 
1256         @disable this(this);
1257 
1258         ~this() nothrow @trusted @nogc
1259         {
1260             free(_ptr);
1261             freeCount += 1;
1262         }
1263 
1264         import nxt.gc_traits : NoGc;
1265         @NoGc E* _ptr;
1266     }
1267 
1268     /* D compilers cannot currently move stuff efficiently when using
1269        std.algorithm.mutation.move. A final dtor call to the cleared sourced is
1270        always done.
1271     */
1272     const size_t extraDtor = 1;
1273 
1274     alias A = DynamicArray!(S);
1275     static assert(!mustAddGCRange!A);
1276     alias AA = DynamicArray!(A);
1277     static assert(!mustAddGCRange!AA);
1278 
1279     assert(mallocCount == 0);
1280 
1281     {
1282         A a;
1283         a.insertBack(S(11));
1284         assert(mallocCount == 1);
1285         assert(freeCount == extraDtor + 0);
1286     }
1287 
1288     assert(freeCount == extraDtor + 1);
1289 
1290     // assert(a.front !is S(11));
1291     // assert(a.back !is S(11));
1292     // a.insertBack(S(12));
1293 }
1294 
1295 /// test `OutputRange` behaviour with std.format
1296 version(none)                   // TODO: replace with other exercise of std.format
1297 @safe pure /*TODO: nothrow @nogc*/ unittest
1298 {
1299     import std.format : formattedWrite;
1300     const x = "42";
1301     alias A = DynamicArray!(char);
1302     A a;
1303     a.formattedWrite!("x : %s")(x);
1304     assert(a == "x : 42");
1305 }
1306 
1307 /// test emplaceWithMovedElements
1308 @trusted pure nothrow @nogc unittest
1309 {
1310     alias A = DynamicArray!(char);
1311 
1312     auto ae = ['a', 'b'].s;
1313 
1314     A a = void;
1315     A.emplaceWithMovedElements(&a, ae[]);
1316 
1317     assert(a.length == ae.length);
1318     assert(a.capacity == ae.length);
1319     assert(a[] == ae);
1320 }
1321 
1322 /// test emplaceWithCopiedElements
1323 @trusted pure nothrow @nogc unittest
1324 {
1325     alias A = DynamicArray!(char);
1326 
1327     auto ae = ['a', 'b'].s;
1328 
1329     A a = void;
1330     A.emplaceWithCopiedElements(&a, ae[]);
1331 
1332     assert(a.length == ae.length);
1333     assert(a.capacity == ae.length);
1334     assert(a[] == ae);
1335 }
1336 
1337 @safe pure nothrow @nogc unittest
1338 {
1339     alias T = int;
1340     alias A = DynamicArray!(T, Mallocator, uint);
1341     const a = A(17);
1342     assert(a[] == [17].s);
1343 }
1344 
1345 /// check duplication via `dup`
1346 @trusted pure nothrow @nogc unittest
1347 {
1348     alias T = int;
1349     alias A = DynamicArray!(T);
1350 
1351     static assert(!__traits(compiles, { A b = a; })); // copying disabled
1352 
1353     auto a = A([10, 11, 12].s);
1354     auto b = a.dup;
1355     assert(a == b);
1356     assert(&a[0] !is &b[0]);
1357 }
1358 
1359 /// element type is a class
1360 @safe pure nothrow unittest
1361 {
1362     class T
1363     {
1364         this (int x)
1365         {
1366             this.x = x;
1367         }
1368         ~this() nothrow @nogc { x = 42; }
1369         int x;
1370     }
1371     alias A = DynamicArray!(T);
1372     auto a = A([new T(10),
1373                 new T(11),
1374                 new T(12)].s);
1375     assert(a.length == 3);
1376     a.remove!(_ => _.x == 12);
1377     assert(a.length == 2);
1378 }
1379 
1380 /// check filtered removal via `remove`
1381 @safe pure nothrow @nogc unittest
1382 {
1383     struct T
1384     {
1385         int value;
1386     }
1387 
1388     alias A = DynamicArray!(T);
1389 
1390     static assert(!__traits(compiles, { A b = a; })); // copying disabled
1391 
1392     auto a = A([T(10), T(11), T(12)].s);
1393 
1394     assert(a.remove!"a.value == 13" == 0);
1395     assert(a[] == [T(10), T(11), T(12)].s);
1396 
1397     assert(a.remove!"a.value >= 12" == 1);
1398     assert(a[] == [T(10), T(11)].s);
1399 
1400     assert(a.remove!(_ => _.value == 10) == 1);
1401     assert(a[] == [T(11)].s);
1402 
1403     assert(a.remove!(_ => _.value == 11) == 1);
1404     assert(a.empty);
1405 }
1406 
1407 /// construct from map range
1408 @safe pure nothrow unittest
1409 {
1410     import std.algorithm.iteration : map;
1411     alias T = int;
1412     alias A = DynamicArray!(T);
1413 
1414     A a = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => _^^2));
1415     assert(a[] == [100, 400, 900].s);
1416     a.popBackN(2);
1417     assert(a.length == 1);
1418     a.popBackN(1);
1419     assert(a.empty);
1420 
1421     A b = A([10, 20, 30].s[].map!(_ => _^^2));
1422     assert(b[] == [100, 400, 900].s);
1423     b.popBackN(2);
1424     assert(b.length == 1);
1425     b.popBackN(1);
1426     assert(b.empty);
1427 
1428     A c = A([10, 20, 30].s[]);
1429     assert(c[] == [10, 20, 30].s);
1430 }
1431 
1432 /// construct from map range
1433 @trusted pure nothrow unittest
1434 {
1435     alias T = int;
1436     alias A = DynamicArray!(T);
1437 
1438     import std.typecons : RefCounted;
1439     RefCounted!A x;
1440 
1441     auto z = [1, 2, 3].s;
1442     x ~= z[];
1443 
1444     auto y = x;
1445     assert(y[] == z);
1446 
1447     const _ = x.toHash;
1448 }
1449 
1450 /// construct from static array
1451 @trusted pure nothrow @nogc unittest
1452 {
1453     alias T = uint;
1454     alias A = DynamicArray!(T);
1455 
1456     ushort[3] a = [1, 2, 3];
1457 
1458     const x = A(a);
1459     assert(x == a);
1460     assert(x == a[]);
1461 }
1462 
1463 /// construct from static array slice
1464 @trusted pure nothrow @nogc unittest
1465 {
1466     alias T = uint;
1467     alias A = DynamicArray!(T);
1468 
1469     ushort[3] a = [1, 2, 3];
1470     ushort[] b = a[];
1471 
1472     const y = A(b);          // cannot construct directly from `a[]` because its type is `ushort[3]`
1473     assert(y == a);
1474     assert(y == a[]);
1475 }
1476 
1477 /// GCAllocator
1478 @trusted pure nothrow unittest
1479 {
1480     import std.experimental.allocator.gc_allocator : GCAllocator;
1481     alias T = int;
1482     alias A = DynamicArray!(T, GCAllocator);
1483     const A a;
1484     assert(a.length == 0);
1485 }
1486 
1487 /// construct with slices as element types
1488 @trusted pure nothrow unittest
1489 {
1490     alias A = DynamicArray!(string);
1491     const A a;
1492     assert(a.length == 0);
1493     alias B = DynamicArray!(char[]);
1494     const B b;
1495     assert(b.length == 0);
1496 }
1497 
1498 /** Variant of `DynamicArray` with copy construction (postblit) enabled.
1499  *
1500  * See_Also: suppressing.d
1501  * See_Also: http://forum.dlang.org/post/eitlbtfbavdphbvplnrk@forum.dlang.org
1502  */
1503 struct BasicCopyableArray
1504 {
1505     /** TODO: implement using instructions at:
1506      * http://forum.dlang.org/post/eitlbtfbavdphbvplnrk@forum.dlang.org
1507      */
1508 }
1509 
1510 /// TODO: Move to Phobos.
1511 private enum bool isRefIterable(T) = is(typeof({ foreach (ref elem; T.init) {} }));
1512 
1513 version(unittest)
1514 {
1515     import nxt.array_help : s;
1516     import nxt.dip_traits : hasPreviewDIP1000;
1517 }