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.functional : unaryFun;
863 
864 /** Remove all elements matching `predicate`.
865 
866     Returns: number of elements that were removed.
867 
868     TODO: implement version that doesn't use a temporary array `tmp`, which is
869     probably faster for small arrays.
870  */
871 size_t remove(alias predicate, C)(ref C c) @trusted @("complexity", "O(length)")
872 if (is(C == DynamicArray!(_), _...) &&
873     is(typeof(unaryFun!predicate(C.init[0]))))
874 {
875     C tmp;
876     size_t count = 0;
877     foreach (immutable i; 0 .. c.length)
878     {
879         if (unaryFun!predicate(c[i]))
880         {
881             count += 1;
882             import core.internal.traits : hasElaborateDestructor;
883             import nxt.container.traits : mustAddGCRange;
884             alias T = typeof(c[i]);
885             static if (hasElaborateDestructor!(T))
886                 .destroy(c[i]);
887             else static if (mustAddGCRange!(T))
888                 c[i] = T.init;    // avoid GC mark-phase dereference
889         }
890         else
891             tmp.insertBackMove(c[i]); // TODO: remove unnecessary clearing of `_mptr[i]`
892     }
893 
894     c.freeStore();
895 
896     import core.lifetime : moveEmplace;
897     moveEmplace(tmp, c);
898 
899     return count;
900 }
901 
902 /// construct and append from slices
903 @safe pure nothrow @nogc unittest
904 {
905     alias T = int;
906     alias A = DynamicArray!(T, Mallocator, uint);
907     static if (size_t.sizeof == 8) // only 64-bit
908         static assert(A.sizeof == 2 * size_t.sizeof); // only two words
909 
910     auto a = A([10, 11, 12].s);
911 
912     a ~= a[];
913     assert(a[] == [10, 11, 12,
914                    10, 11, 12].s);
915 
916     a ~= false;
917     assert(a[] == [10, 11, 12,
918                    10, 11, 12, 0].s);
919 }
920 
921 ///
922 @safe pure nothrow @nogc unittest
923 {
924     alias T = int;
925     alias A = DynamicArray!(T);
926 
927     A a;
928 
929     a.length = 1;
930     assert(a.length == 1);
931     assert(a.capacity >= 1);
932 
933     a[0] = 10;
934 
935     a.insertBack(11, 12);
936 
937     a ~= T.init;
938     a.insertBack([3].s);
939     assert(a[] == [10, 11, 12, 0, 3].s);
940 
941     import std.algorithm.iteration : filter;
942 
943     a.insertBack([42].s[].filter!(_ => _ is 42));
944     assert(a[] == [10, 11, 12, 0, 3, 42].s);
945 
946     a.insertBack([42].s[].filter!(_ => _ !is 42));
947     assert(a[] == [10, 11, 12, 0, 3, 42].s);
948 
949     a ~= a[];
950     assert(a[] == [10, 11, 12, 0, 3, 42,
951                    10, 11, 12, 0, 3, 42].s);
952 }
953 
954 ///
955 @safe pure nothrow @nogc unittest
956 {
957     alias T = int;
958     alias A = DynamicArray!(T);
959 
960     A a;                        // default construction allowed
961     assert(a.empty);
962     assert(a.length == 0);
963     assert(a.capacity == 0);
964     assert(a[] == []);
965 
966     auto b = DynamicArray!int.withLength(3);
967     assert(!b.empty);
968     assert(b.length == 3);
969     assert(b.capacity == 3);
970     b[0] = 1;
971     b[1] = 2;
972     b[2] = 3;
973     assert(b[] == [1, 2, 3].s);
974 
975     b[] = [4, 5, 6].s;
976     assert(b[] == [4, 5, 6].s);
977 
978     const c = DynamicArray!int.withCapacity(3);
979     assert(c.empty);
980     assert(c.capacity == 3);
981     assert(c[] == []);
982 
983 	static if (hasPreviewDIP1000)
984 		static assert(!__traits(compiles, { T[] f() @safe { A a; return a[]; } }));
985 
986     const e = DynamicArray!int([1, 2, 3, 4].s);
987     assert(e.length == 4);
988     assert(e[] == [1, 2, 3, 4].s);
989 }
990 
991 ///
992 @trusted pure nothrow @nogc unittest
993 {
994     alias T = int;
995     alias A = DynamicArray!(T);
996 
997     auto a = A([1, 2, 3].s);
998     A b = a.dup;                // copy construction enabled
999 
1000     assert(a[] == b[]);          // same content
1001     assert(&a[0] !is &b[0]); // but not the same
1002 
1003     assert(b[] == [1, 2, 3].s);
1004     assert(b.length == 3);
1005 
1006     b ~= 4;
1007     assert(a != b);
1008     a.clear();
1009     assert(a != b);
1010     b.clear();
1011     assert(a == b);
1012 
1013     const c = A([1, 2, 3].s);
1014 	assert(c.length == 3);
1015 }
1016 
1017 /// DIP-1000 return ref escape analysis
1018 @safe pure nothrow @nogc unittest
1019 {
1020     alias T = int;
1021     alias A = DynamicArray!T;
1022 
1023 	static if (hasPreviewDIP1000)
1024 		static assert(!__traits(compiles, { T[] leakSlice() @safe pure nothrow @nogc { A a; return a[]; } }));
1025 
1026     T* leakPointer() @safe pure nothrow @nogc
1027     {
1028         A a;
1029         return a._store.ptr;    // TODO: shouldn't compile with -dip1000
1030     }
1031 
1032     const _lp = leakPointer();    // TODO: shouldn't compile with -dip1000
1033 }
1034 
1035 version(unittest)
1036 {
1037     private static struct SomeUncopyable
1038     {
1039         @disable this(this);
1040         int _x;
1041     }
1042 }
1043 
1044 /// construct and insert from non-copyable element type passed by value
1045 @safe pure nothrow /*@nogc*/ unittest
1046 {
1047     alias A = DynamicArray!(SomeUncopyable);
1048 
1049     A a = A(SomeUncopyable(17));
1050     assert(a[] == [SomeUncopyable(17)]);
1051 
1052     a.insertBack(SomeUncopyable(18));
1053     assert(a[] == [SomeUncopyable(17),
1054                    SomeUncopyable(18)]);
1055 
1056     a ~= SomeUncopyable(19);
1057     assert(a[] == [SomeUncopyable(17),
1058                    SomeUncopyable(18),
1059                    SomeUncopyable(19)]);
1060 }
1061 
1062 /// construct from slice of uncopyable type
1063 @safe pure nothrow @nogc unittest
1064 {
1065     alias _A = DynamicArray!(SomeUncopyable);
1066     // TODO: can we safely support this?: A a = [SomeUncopyable(17)];
1067 }
1068 
1069 // construct from array with uncopyable elements
1070 @safe pure nothrow @nogc unittest
1071 {
1072     alias A = DynamicArray!(SomeUncopyable);
1073 
1074     const A a;
1075     assert(a.empty);
1076 
1077     // TODO: a.insertBack(A.init);
1078     assert(a.empty);
1079 
1080 	const _ = a.toHash;
1081 }
1082 
1083 // construct from ranges of uncopyable elements
1084 @safe pure nothrow @nogc unittest
1085 {
1086     alias T = SomeUncopyable;
1087     alias A = DynamicArray!T;
1088 
1089     const A a;
1090     assert(a.empty);
1091 
1092     // import std.algorithm.iteration : map, filter;
1093 
1094     // const b = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => T(_^^2))); // hasLength
1095     // assert(b.length == 3);
1096     // assert(b == [T(100), T(400), T(900)].s);
1097 
1098     // const c = A.withElementsOfRange_untested([10, 20, 30].s[].filter!(_ => _ == 30).map!(_ => T(_^^2))); // !hasLength
1099     // assert(c.length == 1);
1100     // assert(c[0].x == 900);
1101 }
1102 
1103 // construct from ranges of copyable elements
1104 @safe pure nothrow @nogc unittest
1105 {
1106     alias T = int;
1107     alias A = DynamicArray!T;
1108 
1109     const A a;
1110     assert(a.empty);
1111 
1112     import std.algorithm.iteration : map, filter;
1113 
1114     const b = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => T(_^^2))); // hasLength
1115     assert(b.length == 3);
1116     assert(b == [T(100), T(400), T(900)].s);
1117 
1118     () @trusted {               // TODO: remove @trusted
1119         const c = A.withElementsOfRange_untested([10, 20, 30].s[].filter!(_ => _ == 30).map!(_ => T(_^^2))); // !hasLength
1120         assert(c == [T(900)].s);
1121     } ();
1122 }
1123 
1124 /// construct with string as element type that needs GC-range
1125 @safe pure nothrow @nogc unittest
1126 {
1127     alias T = string;
1128     alias A = DynamicArray!(T);
1129 
1130     A a;
1131     a ~= `alpha`;
1132     a ~= `beta`;
1133     a ~= [`gamma`, `delta`].s;
1134     assert(a[] == [`alpha`, `beta`, `gamma`, `delta`].s);
1135 
1136     const b = [`epsilon`].s;
1137 
1138     a.insertBack(b);
1139     assert(a[] == [`alpha`, `beta`, `gamma`, `delta`, `epsilon`].s);
1140 
1141     a ~= b;
1142     assert(a[] == [`alpha`, `beta`, `gamma`, `delta`, `epsilon`, `epsilon`].s);
1143 }
1144 
1145 /// convert to string
1146 version(none)                   // TODO: make this work
1147 unittest
1148 {
1149     alias T = int;
1150     alias A = DynamicArray!(T);
1151 
1152     DynamicArray!char sink;
1153     A([1, 2, 3]).toString(sink.put);
1154 }
1155 
1156 /// iteration over mutable elements
1157 @safe pure nothrow @nogc unittest
1158 {
1159     alias T = int;
1160     alias A = DynamicArray!(T);
1161 
1162     auto a = A([1, 2, 3].s);
1163 
1164     foreach (immutable i, const e; a)
1165         assert(i + 1 == e);
1166 }
1167 
1168 /// iteration over `const`ant elements
1169 @safe pure nothrow @nogc unittest
1170 {
1171     alias T = const(int);
1172     alias A = DynamicArray!(T);
1173 
1174     auto a = A([1, 2, 3].s);
1175 
1176     foreach (immutable i, const e; a)
1177         assert(i + 1 == e);
1178 }
1179 
1180 /// iteration over immutable elements
1181 @safe pure nothrow @nogc unittest
1182 {
1183     alias T = immutable(int);
1184     alias A = DynamicArray!(T);
1185 
1186     auto a = A([1, 2, 3].s);
1187 
1188     foreach (immutable i, const e; a)
1189         assert(i + 1 == e);
1190 }
1191 
1192 /// removal
1193 @safe pure nothrow @nogc unittest
1194 {
1195     alias T = int;
1196     alias A = DynamicArray!(T);
1197 
1198     auto a = A([1, 2, 3].s);
1199     assert(a == [1, 2, 3].s);
1200 
1201     assert(a.takeFront() == 1);
1202     assert(a == [2, 3].s);
1203 
1204     a.popAt(1);
1205     assert(a == [2].s);
1206 
1207     a.popAt(0);
1208     assert(a == []);
1209 
1210     a.insertBack(11);
1211     assert(a == [11].s);
1212 
1213     assert(a.takeBack == 11);
1214 
1215     a.insertBack(17);
1216     assert(a == [17].s);
1217     a.popBack();
1218     assert(a.empty);
1219 
1220     a.insertBack([11, 12, 13, 14, 15].s[]);
1221     a.popAt(2);
1222     assert(a == [11, 12, 14, 15].s);
1223     a.popAt(0);
1224     assert(a == [12, 14, 15].s);
1225     a.popAt(2);
1226 
1227     assert(a == [12, 14].s);
1228 
1229     a ~= a;
1230 }
1231 
1232 /// removal
1233 @safe pure nothrow unittest
1234 {
1235     import nxt.container.traits : mustAddGCRange;
1236 
1237     size_t mallocCount = 0;
1238     size_t freeCount = 0;
1239 
1240     struct S
1241     {
1242         @safe pure nothrow @nogc:
1243 
1244         alias E = int;
1245 
1246         import nxt.qcmeman : malloc, free;
1247 
1248         this(E x) @trusted
1249         {
1250             _ptr = cast(E*)malloc(E.sizeof);
1251             mallocCount += 1;
1252             *_ptr = x;
1253         }
1254 
1255         @disable this(this);
1256 
1257         ~this() nothrow @trusted @nogc
1258         {
1259             free(_ptr);
1260             freeCount += 1;
1261         }
1262 
1263         import nxt.gc_traits : NoGc;
1264         @NoGc E* _ptr;
1265     }
1266 
1267     /* D compilers cannot currently move stuff efficiently when using
1268        std.algorithm.mutation.move. A final dtor call to the cleared sourced is
1269        always done.
1270     */
1271     const size_t extraDtor = 1;
1272 
1273     alias A = DynamicArray!(S);
1274     static assert(!mustAddGCRange!A);
1275     alias AA = DynamicArray!(A);
1276     static assert(!mustAddGCRange!AA);
1277 
1278     assert(mallocCount == 0);
1279 
1280     {
1281         A a;
1282         a.insertBack(S(11));
1283         assert(mallocCount == 1);
1284         assert(freeCount == extraDtor + 0);
1285     }
1286 
1287     assert(freeCount == extraDtor + 1);
1288 
1289     // assert(a.front !is S(11));
1290     // assert(a.back !is S(11));
1291     // a.insertBack(S(12));
1292 }
1293 
1294 /// test `OutputRange` behaviour with std.format
1295 version(none)                   // TODO: replace with other exercise of std.format
1296 @safe pure /*TODO: nothrow @nogc*/ unittest
1297 {
1298     import std.format : formattedWrite;
1299     const x = "42";
1300     alias A = DynamicArray!(char);
1301     A a;
1302     a.formattedWrite!("x : %s")(x);
1303     assert(a == "x : 42");
1304 }
1305 
1306 /// test emplaceWithMovedElements
1307 @trusted pure nothrow @nogc unittest
1308 {
1309     alias A = DynamicArray!(char);
1310 
1311     auto ae = ['a', 'b'].s;
1312 
1313     A a = void;
1314     A.emplaceWithMovedElements(&a, ae[]);
1315 
1316     assert(a.length == ae.length);
1317     assert(a.capacity == ae.length);
1318     assert(a[] == ae);
1319 }
1320 
1321 /// test emplaceWithCopiedElements
1322 @trusted pure nothrow @nogc unittest
1323 {
1324     alias A = DynamicArray!(char);
1325 
1326     auto ae = ['a', 'b'].s;
1327 
1328     A a = void;
1329     A.emplaceWithCopiedElements(&a, ae[]);
1330 
1331     assert(a.length == ae.length);
1332     assert(a.capacity == ae.length);
1333     assert(a[] == ae);
1334 }
1335 
1336 @safe pure nothrow @nogc unittest
1337 {
1338     alias T = int;
1339     alias A = DynamicArray!(T, Mallocator, uint);
1340     const a = A(17);
1341     assert(a[] == [17].s);
1342 }
1343 
1344 /// check duplication via `dup`
1345 @trusted pure nothrow @nogc unittest
1346 {
1347     alias T = int;
1348     alias A = DynamicArray!(T);
1349 
1350     static assert(!__traits(compiles, { A b = a; })); // copying disabled
1351 
1352     auto a = A([10, 11, 12].s);
1353     auto b = a.dup;
1354     assert(a == b);
1355     assert(&a[0] !is &b[0]);
1356 }
1357 
1358 /// element type is a class
1359 @safe pure nothrow unittest
1360 {
1361     class T
1362     {
1363         this (int x)
1364         {
1365             this.x = x;
1366         }
1367         ~this() nothrow @nogc { x = 42; }
1368         int x;
1369     }
1370     alias A = DynamicArray!(T);
1371     auto a = A([new T(10),
1372                 new T(11),
1373                 new T(12)].s);
1374     assert(a.length == 3);
1375     a.remove!(_ => _.x == 12);
1376     assert(a.length == 2);
1377 }
1378 
1379 /// check filtered removal via `remove`
1380 @safe pure nothrow @nogc unittest
1381 {
1382     struct T
1383     {
1384         int value;
1385     }
1386 
1387     alias A = DynamicArray!(T);
1388 
1389     static assert(!__traits(compiles, { A b = a; })); // copying disabled
1390 
1391     auto a = A([T(10), T(11), T(12)].s);
1392 
1393     assert(a.remove!"a.value == 13" == 0);
1394     assert(a[] == [T(10), T(11), T(12)].s);
1395 
1396     assert(a.remove!"a.value >= 12" == 1);
1397     assert(a[] == [T(10), T(11)].s);
1398 
1399     assert(a.remove!(_ => _.value == 10) == 1);
1400     assert(a[] == [T(11)].s);
1401 
1402     assert(a.remove!(_ => _.value == 11) == 1);
1403     assert(a.empty);
1404 }
1405 
1406 /// construct from map range
1407 @safe pure nothrow unittest
1408 {
1409     import std.algorithm.iteration : map;
1410     alias T = int;
1411     alias A = DynamicArray!(T);
1412 
1413     A a = A.withElementsOfRange_untested([10, 20, 30].s[].map!(_ => _^^2));
1414     assert(a[] == [100, 400, 900].s);
1415     a.popBackN(2);
1416     assert(a.length == 1);
1417     a.popBackN(1);
1418     assert(a.empty);
1419 
1420     A b = A([10, 20, 30].s[].map!(_ => _^^2));
1421     assert(b[] == [100, 400, 900].s);
1422     b.popBackN(2);
1423     assert(b.length == 1);
1424     b.popBackN(1);
1425     assert(b.empty);
1426 
1427     A c = A([10, 20, 30].s[]);
1428     assert(c[] == [10, 20, 30].s);
1429 }
1430 
1431 /// construct from map range
1432 @trusted pure nothrow unittest
1433 {
1434     alias T = int;
1435     alias A = DynamicArray!(T);
1436 
1437     import std.typecons : RefCounted;
1438     RefCounted!A x;
1439 
1440     auto z = [1, 2, 3].s;
1441     x ~= z[];
1442 
1443     auto y = x;
1444     assert(y[] == z);
1445 
1446     const _ = x.toHash;
1447 }
1448 
1449 /// construct from static array
1450 @trusted pure nothrow @nogc unittest
1451 {
1452     alias T = uint;
1453     alias A = DynamicArray!(T);
1454 
1455     ushort[3] a = [1, 2, 3];
1456 
1457     const x = A(a);
1458     assert(x == a);
1459     assert(x == a[]);
1460 }
1461 
1462 /// construct from static array slice
1463 @trusted pure nothrow @nogc unittest
1464 {
1465     alias T = uint;
1466     alias A = DynamicArray!(T);
1467 
1468     ushort[3] a = [1, 2, 3];
1469     ushort[] b = a[];
1470 
1471     const y = A(b);          // cannot construct directly from `a[]` because its type is `ushort[3]`
1472     assert(y == a);
1473     assert(y == a[]);
1474 }
1475 
1476 /// GCAllocator
1477 @trusted pure nothrow unittest
1478 {
1479     import std.experimental.allocator.gc_allocator : GCAllocator;
1480     alias T = int;
1481     alias A = DynamicArray!(T, GCAllocator);
1482     const A a;
1483     assert(a.length == 0);
1484 }
1485 
1486 /// construct with slices as element types
1487 @trusted pure nothrow unittest
1488 {
1489     alias A = DynamicArray!(string);
1490     const A a;
1491     assert(a.length == 0);
1492     alias B = DynamicArray!(char[]);
1493     const B b;
1494     assert(b.length == 0);
1495 }
1496 
1497 /** Variant of `DynamicArray` with copy construction (postblit) enabled.
1498  *
1499  * See_Also: suppressing.d
1500  * See_Also: http://forum.dlang.org/post/eitlbtfbavdphbvplnrk@forum.dlang.org
1501  */
1502 struct BasicCopyableArray
1503 {
1504     /** TODO: implement using instructions at:
1505      * http://forum.dlang.org/post/eitlbtfbavdphbvplnrk@forum.dlang.org
1506      */
1507 }
1508 
1509 /// TODO: Move to Phobos.
1510 private enum bool isRefIterable(T) = is(typeof({ foreach (ref elem; T.init) {} }));
1511 
1512 version(unittest)
1513 {
1514     import nxt.array_help : s;
1515     import nxt.dip_traits : hasPreviewDIP1000;
1516 }