1 ///
2 module stdx.collections.dlist;
3 
4 import stdx.collections.common;
5 
6 debug(CollectionDList) import std.stdio;
7 
8 version(unittest)
9 {
10     import std.experimental.allocator.mallocator;
11     import std.experimental.allocator.building_blocks.stats_collector;
12     import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
13            allocatorObject, sharedAllocatorObject;
14 
15     private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed);
16 }
17 
18 ///
19 struct DList(T)
20 {
21     import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
22            theAllocator, processAllocator, make, dispose, stateSize;
23     import std.experimental.allocator.building_blocks.affix_allocator;
24     import std.traits : isImplicitlyConvertible;
25     import std.range.primitives : isInputRange, ElementType;
26     import std.variant : Algebraic;
27     import std.conv : emplace;
28     import core.atomic : atomicOp;
29     import std.algorithm.mutation : move;
30 
31 private:
32     struct Node
33     {
34         T _payload;
35         Node *_next;
36         Node *_prev;
37 
38         this(T v, Node *n, Node *p)
39         {
40             debug(CollectionDList) writefln("DList.Node.ctor: Constructing node" ~
41                     " with payload: %s", v);
42             _payload = v;
43             _next = n;
44             _prev = p;
45         }
46 
47         ~this()
48         {
49             debug(CollectionDList) writefln("DList.Node.dtor: Destroying node" ~
50                     " with payload: %s", _payload);
51         }
52     }
53 
54     // State {
55     Node *_head;
56     mixin(allocatorHandler);
57     // }
58 
59     @nogc nothrow pure @trusted
60     void addRef(QualNode, this Q)(QualNode node)
61     {
62         assert(node !is null);
63         cast(void) _allocator.opPrefix!("+=")(cast(void[Node.sizeof])(*node), 1);
64     }
65 
66     void delRef(ref Node *node)
67     {
68         // Will be optimized away, but the type system infers T's safety
69         if (0) { T t = T.init; }
70 
71         assert(node !is null);
72         () @trusted {
73             if (opCmpPrefix!"=="(node, 0))
74             {
75                 dispose(_allocator, node);
76                 node = null;
77             }
78             else
79             {
80                 cast(void) _allocator.opPrefix!("-=")(cast(void[Node.sizeof])(*node), 1);
81             }
82         }();
83     }
84 
85     pragma(inline, true)
86     @nogc nothrow pure @trusted
87     size_t opCmpPrefix(string op)(const Node *node, size_t val) const
88     if ((op == "==") || (op == "<=") || (op == "<") || (op == ">=") || (op == ">"))
89     {
90         return _allocator.opCmpPrefix!op(cast(void[Node.sizeof])(*node), val);
91     }
92 
93     static string immutableInsert(string stuff)
94     {
95         return q{
96             _allocator = immutable AllocatorHandler(allocator);
97             Node *tmpNode;
98             Node *tmpHead;
99             foreach (item;  } ~ stuff ~ q{ )
100             {
101                 Node *newNode;
102                 () @trusted { newNode =
103                     _allocator.make!(Node)(item, null, null);
104                 }();
105                 if (tmpHead is null)
106                 {
107                     tmpHead = tmpNode = newNode;
108                 }
109                 else
110                 {
111                     tmpNode._next = newNode;
112                     newNode._prev = tmpNode;
113                     addRef(newNode._prev);
114                     tmpNode = newNode;
115                 }
116             }
117             _head = (() @trusted => cast(immutable Node*)(tmpHead))();
118         };
119     }
120 
121 public:
122     /**
123      * Constructs a qualified doubly linked list that will use the provided
124      * allocator object. For `immutable` objects, a `RCISharedAllocator` must
125      * be supplied.
126      *
127      * Params:
128      *      allocator = a $(REF RCIAllocator, std,experimental,allocator) or
129      *                  $(REF RCISharedAllocator, std,experimental,allocator)
130      *                  allocator object
131      *
132      * Complexity: $(BIGOH 1)
133      */
134     this(A, this Q)(A allocator)
135     if (!is(Q == shared)
136         && (is(A == RCISharedAllocator) || !is(Q == immutable))
137         && (is(A == RCIAllocator) || is(A == RCISharedAllocator)))
138     {
139         debug(CollectionDList)
140         {
141             writefln("DList.ctor: begin");
142             scope(exit) writefln("DList.ctor: end");
143         }
144         static if (is(Q == immutable) || is(Q == const))
145         {
146             T[] empty;
147             this(allocator, empty);
148         }
149         else
150         {
151             setAllocator(allocator);
152         }
153     }
154 
155     ///
156     @safe unittest
157     {
158         auto dl = DList!int(theAllocator);
159         auto cdl = const DList!int(processAllocator);
160         auto idl = immutable DList!int(processAllocator);
161     }
162 
163     /**
164      * Constructs a qualified doubly linked list out of a number of items.
165      * Because no allocator was provided, the list will use the
166      * $(REF, GCAllocator, std,experimental,allocator).
167      *
168      * Params:
169      *      values = a variable number of items, either in the form of a
170      *               list or as a built-in array
171      *
172      * Complexity: $(BIGOH m), where `m` is the number of items.
173      */
174     this(U, this Q)(U[] values...)
175     if (isImplicitlyConvertible!(U, T))
176     {
177         this(defaultAllocator!(typeof(this)), values);
178     }
179 
180     ///
181     static if (is(T == int))
182     @safe unittest
183     {
184         import std.algorithm.comparison : equal;
185 
186         // Create a list from a list of ints
187         {
188             auto dl = DList!int(1, 2, 3);
189             assert(equal(dl, [1, 2, 3]));
190         }
191         // Create a list from an array of ints
192         {
193             auto dl = DList!int([1, 2, 3]);
194             assert(equal(dl, [1, 2, 3]));
195         }
196         // Create a list from a list from an input range
197         {
198             auto dl = DList!int(1, 2, 3);
199             auto dl2 = DList!int(dl);
200             assert(equal(dl2, [1, 2, 3]));
201         }
202     }
203 
204     /**
205      * Constructs a qualified doubly linked list out of a number of items
206      * that will use the provided allocator object.
207      * For `immutable` objects, a `RCISharedAllocator` must be supplied.
208      *
209      * Params:
210      *      allocator = a $(REF RCIAllocator, std,experimental,allocator) or
211      *                  $(REF RCISharedAllocator, std,experimental,allocator)
212      *                  allocator object
213      *      values = a variable number of items, either in the form of a
214      *               list or as a built-in array
215      *
216      * Complexity: $(BIGOH m), where `m` is the number of items.
217      */
218     this(A, U, this Q)(A allocator, U[] values...)
219     if (!is(Q == shared)
220         && (is(A == RCISharedAllocator) || !is(Q == immutable))
221         && (is(A == RCIAllocator) || is(A == RCISharedAllocator))
222         && isImplicitlyConvertible!(U, T))
223     {
224         debug(CollectionDList)
225         {
226             writefln("DList.ctor: begin");
227             scope(exit) writefln("DList.ctor: end");
228         }
229         static if (is(Q == immutable) || is(Q == const))
230         {
231             mixin(immutableInsert("values"));
232         }
233         else
234         {
235             setAllocator(allocator);
236             insert(0, values);
237         }
238     }
239 
240     /**
241      * Constructs a qualified doubly linked list out of an
242      * $(REF_ALTTEXT input range, isInputRange, std,range,primitives).
243      * Because no allocator was provided, the list will use the
244      * $(REF, GCAllocator, std,experimental,allocator).
245      *
246      * Params:
247      *      stuff = an input range of elements that are implitictly convertible
248      *              to `T`
249      *
250      * Complexity: $(BIGOH m), where `m` is the number of elements in the range.
251      */
252     this(Stuff, this Q)(Stuff stuff)
253     if (isInputRange!Stuff
254         && isImplicitlyConvertible!(ElementType!Stuff, T)
255         && !is(Stuff == T[]))
256     {
257         this(defaultAllocator!(typeof(this)), stuff);
258     }
259 
260     /**
261      * Constructs a qualified doubly linked list out of an
262      * $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
263      * that will use the provided allocator object.
264      * For `immutable` objects, a `RCISharedAllocator` must be supplied.
265      *
266      * Params:
267      *      allocator = a $(REF RCIAllocator, std,experimental,allocator) or
268      *                  $(REF RCISharedAllocator, std,experimental,allocator)
269      *                  allocator object
270      *      stuff = an input range of elements that are implitictly convertible
271      *              to `T`
272      *
273      * Complexity: $(BIGOH m), where `m` is the number of elements in the range.
274      */
275     this(A, Stuff, this Q)(A allocator, Stuff stuff)
276     if (!is(Q == shared)
277         && (is(A == RCISharedAllocator) || !is(Q == immutable))
278         && (is(A == RCIAllocator) || is(A == RCISharedAllocator))
279         && isInputRange!Stuff
280         && isImplicitlyConvertible!(ElementType!Stuff, T)
281         && !is(Stuff == T[]))
282     {
283         debug(CollectionDList)
284         {
285             writefln("DList.ctor: begin");
286             scope(exit) writefln("DList.ctor: end");
287         }
288         static if (is(Q == immutable) || is(Q == const))
289         {
290             mixin(immutableInsert("stuff"));
291         }
292         else
293         {
294             setAllocator(allocator);
295             insert(0, stuff);
296         }
297     }
298 
299     this(this)
300     {
301         debug(CollectionDList)
302         {
303             writefln("DList.postblit: begin");
304             scope(exit) writefln("DList.postblit: end");
305         }
306         _allocator.bootstrap();
307         if (_head !is null)
308         {
309             addRef(_head);
310             debug(CollectionDList) writefln("DList.postblit: Node %s has refcount: %s",
311                     _head._payload, *prefCount(_head));
312         }
313     }
314 
315     // Immutable ctors
316     // Very important to pass the allocator by ref! (Related to postblit bug)
317     private this(NodeQual, AllocQual, this Q)(NodeQual _newHead, ref AllocQual _newAllocator)
318         if (is(typeof(_head) : typeof(_newHead))
319             && (is(Q == immutable) || is(Q == const)))
320     {
321         _head = _newHead;
322         // Needs a bootstrap
323         // bootstrap is the equivalent of incRef
324         _newAllocator.bootstrap();
325         _allocator = _newAllocator;
326         if (_head !is null)
327         {
328             addRef(_head);
329             debug(CollectionDList) writefln("DList.ctor immutable: Node %s has "
330                     ~ "refcount: %s", _head._payload, *prefCount(_head));
331         }
332     }
333 
334     ~this()
335     {
336         debug(CollectionDList)
337         {
338             writefln("DList.dtor: Begin for instance %s of type %s",
339                 cast(size_t)(&this), typeof(this).stringof);
340             scope(exit) writefln("DList.dtor: End for instance %s of type %s",
341                     cast(size_t)(&this), typeof(this).stringof);
342         }
343         if (_head !is null)
344         {
345             delRef(_head);
346             if (_head !is null
347                 && ((_head._prev !is null) || (_head._next !is null)))
348             {
349                 // If it was a single node list, only delRef must be used
350                 // in order to avoid premature/double freeing
351                 destroyUnused(_head);
352             }
353         }
354     }
355 
356     static if (is(T == int))
357     nothrow pure @safe unittest
358     {
359         auto s = DList!int(1, 2, 3);
360 
361         // Infer safety
362         static assert(!__traits(compiles, () @safe { DList!Unsafe(Unsafe(1)); }));
363         static assert(!__traits(compiles, () @safe { auto s = const DList!Unsafe(Unsafe(1)); }));
364         static assert(!__traits(compiles, () @safe { auto s = immutable DList!Unsafe(Unsafe(1)); }));
365 
366         static assert(!__traits(compiles, () @safe { DList!UnsafeDtor(UnsafeDtor(1)); }));
367         static assert(!__traits(compiles, () @safe { auto s = const DList!UnsafeDtor(UnsafeDtor(1)); }));
368         static assert(!__traits(compiles, () @safe { auto s = immutable DList!UnsafeDtor(UnsafeDtor(1)); }));
369 
370         // Infer purity
371         static assert(!__traits(compiles, () pure { DList!Impure(Impure(1)); }));
372         static assert(!__traits(compiles, () pure { auto s = const DList!Impure(Impure(1)); }));
373         static assert(!__traits(compiles, () pure { auto s = immutable DList!Impure(Impure(1)); }));
374 
375         static assert(!__traits(compiles, () pure { DList!ImpureDtor(ImpureDtor(1)); }));
376         static assert(!__traits(compiles, () pure { auto s = const DList!ImpureDtor(ImpureDtor(1)); }));
377         static assert(!__traits(compiles, () pure { auto s = immutable DList!ImpureDtor(ImpureDtor(1)); }));
378 
379         // Infer throwability
380         static assert(!__traits(compiles, () nothrow { DList!Throws(Throws(1)); }));
381         static assert(!__traits(compiles, () nothrow { auto s = const DList!Throws(Throws(1)); }));
382         static assert(!__traits(compiles, () nothrow { auto s = immutable DList!Throws(Throws(1)); }));
383 
384         static assert(!__traits(compiles, () nothrow { DList!ThrowsDtor(ThrowsDtor(1)); }));
385         static assert(!__traits(compiles, () nothrow { auto s = const DList!ThrowsDtor(ThrowsDtor(1)); }));
386         static assert(!__traits(compiles, () nothrow { auto s = immutable DList!ThrowsDtor(ThrowsDtor(1)); }));
387     }
388 
389     private void destroyUnused(Node *startNode)
390     {
391         debug(CollectionDList)
392         {
393             writefln("DList.destoryUnused: begin");
394             scope(exit) writefln("DList.destoryUnused: end");
395         }
396 
397         // Will be optimized away, but the type system infers T's safety
398         if (0) { T t = T.init; }
399 
400         if (startNode is null) return;
401 
402         Node *tmpNode = startNode;
403         bool isCycle = true;
404         while (tmpNode !is null)
405         {
406             if (((tmpNode._next is null || tmpNode._prev is null)
407                   && opCmpPrefix!"=="(tmpNode, 0))
408                 || (tmpNode._next !is null && tmpNode._prev !is null
409                     && opCmpPrefix!"=="(tmpNode, 1)))
410             {
411                 // The last node should always have rc == 0 (only one ref,
412                 // from prev._next)
413                 // The first node should always have rc == 0 (only one ref,
414                 // from next._prev), since we don't take into account
415                 // the head ref (that was deleted either by the dtor or by pop)
416                 // Nodes within the cycle should always have rc == 1
417                 tmpNode = tmpNode._next;
418             }
419             else
420             {
421                 isCycle = false;
422                 break;
423             }
424         }
425 
426         tmpNode = startNode._prev;
427         while (isCycle && tmpNode !is null)
428         {
429             if (((tmpNode._next is null || tmpNode._prev is null)
430                   && opCmpPrefix!"=="(tmpNode, 0))
431                 || (tmpNode._next !is null && tmpNode._prev !is null
432                     && opCmpPrefix!"=="(tmpNode, 1)))
433             {
434                 tmpNode = tmpNode._prev;
435             }
436             else
437             {
438                 isCycle = false;
439                 break;
440             }
441         }
442 
443         if (isCycle)
444         {
445             // We can safely deallocate memory
446             // We could be in the middle of the list so we need to go both
447             // forwards and backwards
448             tmpNode = startNode._next;
449             while (tmpNode !is null)
450             {
451                 Node *oldNode = tmpNode;
452                 tmpNode = tmpNode._next;
453                 () @trusted { dispose(_allocator, oldNode); }();
454             }
455             tmpNode = startNode;
456             while (tmpNode !is null)
457             {
458                 Node *oldNode = tmpNode;
459                 tmpNode = tmpNode._prev;
460                 () @trusted { dispose(_allocator, oldNode); }();
461             }
462         }
463     }
464 
465     /**
466      * Check whether there are no more references to this list instance.
467      *
468      * Returns:
469      *      `true` if this is the only reference to this list instance;
470      *      `false` otherwise.
471      *
472      * Complexity: $(BIGOH n).
473      */
474     bool isUnique() const
475     {
476         debug(CollectionDList)
477         {
478             writefln("DList.isUnique: begin");
479             scope(exit) writefln("DList.isUnique: end");
480         }
481 
482         if (empty)
483         {
484             return true;
485         }
486 
487         Node *tmpNode = (() @trusted => cast(Node*)_head)();
488 
489         // Rewind to the beginning of the list
490         while (tmpNode !is null && tmpNode._prev !is null)
491         {
492             tmpNode = tmpNode._prev;
493         }
494 
495         // For a single node list, head should have rc == 0
496         if (tmpNode._next is null && opCmpPrefix!">"(tmpNode, 0))
497         {
498             return false;
499         }
500 
501         while (tmpNode !is null)
502         {
503             if (tmpNode._next is null || tmpNode._prev is null)
504             {
505                 // The first and last node should have rc == 0 unless the _head
506                 // is pointing to them, in which case rc must be 1
507                 if (((tmpNode is _head) && opCmpPrefix!">"(tmpNode, 1))
508                     || ((tmpNode !is _head) && opCmpPrefix!">"(tmpNode, 0)))
509                 {
510                     return false;
511                 }
512             }
513             else if (((tmpNode is _head) && opCmpPrefix!">"(tmpNode, 2))
514                      || ((tmpNode !is _head) && opCmpPrefix!">"(tmpNode, 1)))
515             {
516                 // Any other node should have rc == 1 unless the _head
517                 // is pointing to it, in which case rc must be 2
518                 return false;
519             }
520             tmpNode = tmpNode._next;
521         }
522         return true;
523     }
524 
525     ///
526     static if (is(T == int))
527     @safe unittest
528     {
529         auto dl = DList!int(24, 42);
530         assert(dl.isUnique);
531         {
532             auto dl2 = dl;
533             assert(!dl.isUnique);
534             dl2.front = 0;
535             assert(dl.front == 0);
536         } // dl2 goes out of scope
537         assert(dl.isUnique);
538     }
539 
540     /**
541      * Check if the list is empty.
542      *
543      * Returns:
544      *      `true` if there are no nodes in the list; `false` otherwise.
545      *
546      * Complexity: $(BIGOH 1).
547      */
548     @nogc nothrow pure @safe
549     bool empty() const
550     {
551         return _head is null;
552     }
553 
554     ///
555     static if (is(T == int))
556     @safe unittest
557     {
558         DList!int dl;
559         assert(dl.empty);
560         size_t pos = 0;
561         dl.insert(pos, 1);
562         assert(!dl.empty);
563     }
564 
565     /**
566      * Provide access to the first element in the list. The user must check
567      * that the list isn't `empty`, prior to calling this function.
568      *
569      * Returns:
570      *      a reference to the first element.
571      *
572      * Complexity: $(BIGOH 1).
573      */
574     ref auto front(this _)()
575     {
576         assert(!empty, "DList.front: List is empty");
577         return _head._payload;
578     }
579 
580     ///
581     static if (is(T == int))
582     @safe unittest
583     {
584         auto dl = DList!int(1, 2, 3);
585         assert(dl.front == 1);
586         dl.front = 0;
587         assert(dl.front == 0);
588     }
589 
590     /**
591      * Advance to the next element in the list. The user must check
592      * that the list isn't `empty`, prior to calling this function.
593      *
594      * If this was the last element in the list and there are no more
595      * references to the current list, then the list and all it's elements
596      * will be destroyed; this will call `T`'s dtor, if one is defined,
597      * and will collect the resources.
598      *
599      * Complexity: usually $(BIGOH 1), worst case $(BIGOH n).
600      */
601     void popFront()
602     {
603         debug(CollectionDList)
604         {
605             writefln("DList.popFront: begin");
606             scope(exit) writefln("DList.popFront: end");
607         }
608         assert(!empty, "DList.popFront: List is empty");
609         Node *tmpNode = _head;
610         _head = _head._next;
611         if (_head !is null)
612         {
613             addRef(_head);
614             delRef(tmpNode);
615         }
616         else
617         {
618             delRef(tmpNode);
619             if (tmpNode !is null
620                 && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
621             {
622                 // If it was a single node list, only delRef must be used
623                 // in order to avoid premature/double freeing
624                 destroyUnused(tmpNode);
625             }
626         }
627     }
628 
629     ///
630     static if (is(T == int))
631     @safe unittest
632     {
633         auto a = [1, 2, 3];
634         auto dl = DList!int(a);
635         size_t i = 0;
636         while (!dl.empty)
637         {
638             assert(dl.front == a[i++]);
639             dl.popFront;
640         }
641         assert(dl.empty);
642     }
643 
644     /**
645      * Go to the previous element in the list. The user must check
646      * that the list isn't `empty`, prior to calling this function.
647      *
648      * If this was the first element in the list and there are no more
649      * references to the current list, then the list and all it's elements
650      * will be destroyed; this will call `T`'s dtor, if one is defined,
651      * and will collect the resources.
652      *
653      * Complexity: usually $(BIGOH 1), worst case $(BIGOH n).
654      */
655     void popPrev()
656     {
657         debug(CollectionDList)
658         {
659             writefln("DList.popPrev: begin");
660             scope(exit) writefln("DList.popPrev: end");
661         }
662         assert(!empty, "DList.popPrev: List is empty");
663         Node *tmpNode = _head;
664         _head = _head._prev;
665         if (_head !is null) {
666             addRef(_head);
667             delRef(tmpNode);
668         }
669         else
670         {
671             delRef(tmpNode);
672             if (tmpNode !is null
673                 && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
674             {
675                 // If it was a single node list, only delRef must be used
676                 // in order to avoid premature/double freeing
677                 destroyUnused(tmpNode);
678             }
679         }
680     }
681 
682     ///
683     static if (is(T == int))
684     @safe unittest
685     {
686         auto dl = DList!int([1, 2, 3]);
687         dl.popFront;
688         assert(dl.front == 2);
689         dl.popPrev;
690         assert(dl.front == 1);
691         dl.popPrev;
692         assert(dl.empty);
693     }
694 
695     /**
696      * Advance to the next element in the list. The user must check
697      * that the list isn't `empty`, prior to calling this function.
698      *
699      * This must be used in order to iterate through a `const` or `immutable`
700      * list. For a mutable list this is equivalent to calling `popFront`.
701      *
702      * Returns:
703      *      a list that starts with the next element in the original list
704      *
705      * Complexity: $(BIGOH 1).
706      */
707     Qualified tail(this Qualified)()
708     {
709         debug(CollectionDList)
710         {
711             writefln("DList.popFront: begin");
712             scope(exit) writefln("DList.popFront: end");
713         }
714         assert(!empty, "DList.popFront: List is empty");
715 
716         static if (is(Qualified == immutable) || is(Qualified == const))
717         {
718             return typeof(this)(_head._next, _allocator);
719         }
720         else
721         {
722             return .tail(this);
723         }
724     }
725 
726     ///
727     static if (is(T == int))
728     @safe unittest
729     {
730         auto idl = immutable DList!int([1, 2, 3]);
731         assert(idl.tail.front == 2);
732     }
733 
734     /**
735      * Eagerly iterate over each element in the list and call `fun` over each
736      * element. This should be used to iterate through `const` and `immutable`
737      * lists.
738      *
739      * Normally, the entire list is iterated. If partial iteration (early stopping)
740      * is desired, `fun` needs to return a value of type
741      * $(REF Flag, std,typecons)`!"each"` (`Yes.each` to continue iteration, or
742      * `No.each` to stop).
743      *
744      * Params:
745      *      fun = unary function to apply on each element of the list.
746      *
747      * Returns:
748      *      `Yes.each` if it has iterated through all the elements in the list,
749      *      or `No.each` otherwise.
750      *
751      * Complexity: $(BIGOH n).
752      */
753     template each(alias fun)
754     {
755         import std.typecons : Flag, Yes, No;
756         import std.functional : unaryFun;
757         import stdx.collections.slist : SList;
758 
759         Flag!"each" each(this Q)()
760         if (is (typeof(unaryFun!fun(T.init))))
761         {
762             alias fn = unaryFun!fun;
763 
764             auto sl = SList!(const DList!T)(this);
765             while (!sl.empty && !sl.front.empty)
766             {
767                 static if (!is(typeof(fn(T.init)) == Flag!"each"))
768                 {
769                     cast(void) fn(sl.front.front);
770                 }
771                 else
772                 {
773                     if (fn(sl.front.front) == No.each)
774                         return No.each;
775                 }
776                 sl ~= sl.front.tail;
777                 sl.popFront;
778             }
779             return Yes.each;
780         }
781     }
782 
783     ///
784     static if (is(T == int))
785     @safe unittest
786     {
787         import std.typecons : Flag, Yes, No;
788 
789         auto idl = immutable DList!int([1, 2, 3]);
790 
791         static bool foo(int x) { return x > 0; }
792 
793         assert(idl.each!foo == Yes.each);
794     }
795 
796     /**
797      * Perform a shallow copy of the list.
798      *
799      * Returns:
800      *      a new reference to the current list.
801      *
802      * Complexity: $(BIGOH 1).
803      */
804     ref Qualified save(this Qualified)()
805     {
806         debug(CollectionDList)
807         {
808             writefln("DList.save: begin");
809             scope(exit) writefln("DList.save: end");
810         }
811         return this;
812     }
813 
814     ///
815     static if (is(T == int))
816     @safe unittest
817     {
818         auto a = [1, 2, 3];
819         auto dl = DList!int(a);
820         size_t i = 0;
821 
822         auto tmp = dl.save;
823         while (!tmp.empty)
824         {
825             assert(tmp.front == a[i++]);
826             tmp.popFront;
827         }
828         assert(tmp.empty);
829         assert(!dl.empty);
830     }
831 
832     /**
833      * Perform a copy of the list. This will create a new list that will copy
834      * the elements of the current list. This will `NOT` call `dup` on the
835      * elements of the list, regardless if `T` defines it or not.
836      *
837      * Returns:
838      *      a new list.
839      *
840      * Complexity: $(BIGOH n).
841      */
842     typeof(this) dup()
843     {
844         debug(CollectionDList)
845         {
846             writefln("DList.dup: begin");
847             scope(exit) writefln("DList.dup: end");
848         }
849 
850         DList!T result;
851         result._allocator = _allocator;
852 
853         // TODO: this should rewind the list
854         static if (is(Q == immutable) || is(Q == const))
855         {
856             auto tmp = this;
857             while(!tmp.empty)
858             {
859                 result ~= tmp.front;
860                 tmp = tmp.tail;
861             }
862         }
863         else
864         {
865             result.insert(0, this);
866         }
867         return result;
868     }
869 
870     ///
871     static if (is(T == int))
872     @safe unittest
873     {
874         import std.algorithm.comparison : equal;
875 
876         auto dl = DList!int(1, 2, 3);
877         auto dlDup = dl.dup;
878         assert(equal(dl, dlDup));
879         dlDup.front = 0;
880         assert(!equal(dl, dlDup));
881         assert(dlDup.front == 0);
882         assert(dl.front == 1);
883     }
884 
885     /**
886      * Inserts the elements of an
887      * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a
888      * variable number of items, at the given `pos`.
889      *
890      * If no allocator was provided when the list was created, the
891      * $(REF, GCAllocator, std,experimental,allocator) will be used.
892      *
893      * Params:
894      *      pos = a positive integral
895      *      stuff = an input range of elements that are implitictly convertible
896      *              to `T`; a variable number of items either in the form of a
897      *              list or as a built-in array
898      *
899      * Returns:
900      *      the number of elements inserted
901      *
902      * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range.
903      */
904     size_t insert(Stuff)(size_t pos, Stuff stuff)
905     if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
906     {
907         debug(CollectionDList)
908         {
909             writefln("DList.insert: begin");
910             scope(exit) writefln("DList.insert: end");
911         }
912 
913         // Will be optimized away, but the type system infers T's safety
914         if (0) { T t = T.init; }
915 
916         // Ensure we have an allocator. If it was already set, this will do nothing
917         auto a = threadAllocatorObject();
918         setAllocator(a);
919 
920         size_t result;
921         Node *tmpNode;
922         Node *tmpHead;
923         foreach (item; stuff)
924         {
925             Node *newNode;
926             () @trusted { newNode = _allocator.make!(Node)(item, null, null); }();
927             if (tmpHead is null)
928             {
929                 tmpHead = tmpNode = newNode;
930             }
931             else
932             {
933                 tmpNode._next = newNode;
934                 newNode._prev = tmpNode;
935                 addRef(newNode._prev);
936                 tmpNode = newNode;
937             }
938             ++result;
939         }
940 
941         if (!tmpNode)
942         {
943             return 0;
944         }
945 
946         if (!_head) assert(pos == 0);
947 
948         size_t initPos = pos;
949         Node *needle = _head;
950         while (pos && needle._next !is null)
951         {
952             needle = needle._next;
953             --pos;
954         }
955 
956         // Check if we need to insert at the back of the list
957         if (initPos != 0 && needle._next is null && pos >= 1)
958         {
959             // We need to insert at the back of the list
960             assert(pos == 1, "Index out of range");
961             needle._next = tmpHead;
962             tmpHead._prev = needle;
963             addRef(needle);
964             return result;
965         }
966         assert(pos == 0, "Index out of range");
967 
968         tmpNode._next = needle;
969         if (needle !is null)
970         {
971             addRef(needle);
972             if (needle._prev !is null)
973             {
974                 tmpHead._prev = needle._prev;
975                 needle._prev._next = tmpHead;
976                 // Inc ref only when tmpHead will be the new head of the list
977                 if (initPos == 0)
978                 {
979                     addRef(tmpHead);
980                 }
981 
982                 // Delete extra ref, since we already added the ref earlier
983                 // through tmpNode._next
984                 delRef(needle);
985             }
986             if (initPos == 0)
987             {
988                 // Pass the ref to the new head
989                 delRef(needle);
990             }
991             assert(needle !is null);
992             needle._prev = tmpNode;
993             if (tmpHead == tmpNode)
994             {
995                 addRef(tmpHead);
996             }
997             else
998             {
999                 addRef(needle._prev);
1000             }
1001         }
1002 
1003         if (initPos == 0)
1004         {
1005             _head = tmpHead;
1006         }
1007         return result;
1008     }
1009 
1010     /// ditto
1011     size_t insert(Stuff)(size_t pos, Stuff[] stuff...)
1012     if (isImplicitlyConvertible!(Stuff, T))
1013     {
1014         return insert(pos, stuff);
1015     }
1016 
1017     ///
1018     static if (is(T == int))
1019     @safe unittest
1020     {
1021         import std.algorithm.comparison : equal;
1022 
1023         auto d = DList!int(4, 5);
1024         DList!int dl;
1025         assert(dl.empty);
1026 
1027         size_t pos = 0;
1028         pos += dl.insert(pos, 1);
1029         pos += dl.insert(pos, [2, 3]);
1030         assert(equal(dl, [1, 2, 3]));
1031 
1032         // insert from an input range
1033         pos += dl.insert(pos, d);
1034         assert(equal(dl, [1, 2, 3, 4, 5]));
1035         d.front = 0;
1036         assert(equal(dl, [1, 2, 3, 4, 5]));
1037     }
1038 
1039     /**
1040      * Inserts the elements of an
1041      * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a
1042      * variable number of items, at the end of the list.
1043      *
1044      * If no allocator was provided when the list was created, the
1045      * $(REF, GCAllocator, std,experimental,allocator) will be used.
1046      *
1047      * Params:
1048      *      stuff = an input range of elements that are implitictly convertible
1049      *              to `T`; a variable number of items either in the form of a
1050      *              list or as a built-in array
1051      *
1052      * Returns:
1053      *      the number of elements inserted
1054      *
1055      * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range.
1056      */
1057     size_t insertBack(Stuff)(Stuff stuff)
1058     if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
1059     {
1060         debug(CollectionDList)
1061         {
1062             writefln("DList.insertBack: begin");
1063             scope(exit) writefln("DList.insertBack: end");
1064         }
1065 
1066         // Will be optimized away, but the type system infers T's safety
1067         if (0) { T t = T.init; }
1068 
1069         // Ensure we have an allocator. If it was already set, this will do nothing
1070         auto a = threadAllocatorObject();
1071         setAllocator(a);
1072 
1073         size_t result;
1074         Node *tmpNode;
1075         Node *tmpHead;
1076         foreach (item; stuff)
1077         {
1078             Node *newNode;
1079             () @trusted { newNode = _allocator.make!(Node)(item, null, null); }();
1080             if (tmpHead is null)
1081             {
1082                 tmpHead = tmpNode = newNode;
1083             }
1084             else
1085             {
1086                 tmpNode._next = newNode;
1087                 newNode._prev = tmpNode;
1088                 addRef(newNode._prev);
1089                 tmpNode = newNode;
1090             }
1091             ++result;
1092         }
1093 
1094         if (!tmpNode)
1095         {
1096             return 0;
1097         }
1098 
1099         if (_head is null)
1100         {
1101             _head = tmpHead;
1102         }
1103         else
1104         {
1105             Node *endNode;
1106             for (endNode = _head; endNode._next !is null; endNode = endNode._next) { }
1107             endNode._next = tmpHead;
1108             // don't addRef(tmpHead) since the ref will pass from tmpHead to
1109             // endNode._next when tmpHead's scope ends
1110             tmpHead._prev = endNode;
1111             addRef(endNode);
1112         }
1113 
1114         return result;
1115     }
1116 
1117     /// ditto
1118     size_t insertBack(Stuff)(Stuff[] stuff...)
1119     if (isImplicitlyConvertible!(Stuff, T))
1120     {
1121         return insertBack(stuff);
1122     }
1123 
1124     ///
1125     static if (is(T == int))
1126     @safe unittest
1127     {
1128         import std.algorithm.comparison : equal;
1129 
1130         auto d = DList!int(4, 5);
1131         DList!int dl;
1132         assert(dl.empty);
1133 
1134         dl.insertBack(1);
1135         dl.insertBack([2, 3]);
1136         assert(equal(dl, [1, 2, 3]));
1137 
1138         // insert from an input range
1139         dl.insertBack(d);
1140         assert(equal(dl, [1, 2, 3, 4, 5]));
1141         d.front = 0;
1142         assert(equal(dl, [1, 2, 3, 4, 5]));
1143     }
1144 
1145     /**
1146      * Create a new list that results from the concatenation of this list
1147      * with `rhs`.
1148      *
1149      * Params:
1150      *      rhs = can be an element that is implicitly convertible to `T`, an
1151      *            input range of such elements, or another doubly linked list
1152      *
1153      * Returns:
1154      *      the newly created list
1155      *
1156      * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`.
1157      */
1158     auto ref opBinary(string op, U)(auto ref U rhs)
1159         if (op == "~" &&
1160             //(is (U : const typeof(this))
1161             (is (U == typeof(this))
1162              || is (U : T)
1163              || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
1164             ))
1165     {
1166         debug(CollectionDList)
1167         {
1168             writefln("DList.opBinary!~: begin");
1169             scope(exit) writefln("DList.opBinary!~: end");
1170         }
1171 
1172         auto newList = this.dup();
1173         newList.insertBack(rhs);
1174         return newList;
1175     }
1176 
1177     ///
1178     static if (is(T == int))
1179     @safe unittest
1180     {
1181         import std.algorithm.comparison : equal;
1182 
1183         auto dl = DList!int(1);
1184         auto dl2 = dl ~ 2;
1185 
1186         assert(equal(dl2, [1, 2]));
1187         dl.front = 0;
1188         assert(equal(dl2, [1, 2]));
1189     }
1190 
1191     /**
1192      * Assign `rhs` to this list. The current list will now become another
1193      * reference to `rhs`, unless `rhs` is `null`, in which case the current
1194      * list will become empty. If `rhs` refers to the current list nothing will
1195      * happen.
1196      *
1197      * All the previous list elements that have no more references to them
1198      * will be destroyed; this leads to a $(BIGOH n) complexity.
1199      *
1200      * Params:
1201      *      rhs = a reference to a doubly linked list
1202      *
1203      * Returns:
1204      *      a reference to this list
1205      *
1206      * Complexity: $(BIGOH n).
1207      */
1208     auto ref opAssign()(auto ref typeof(this) rhs)
1209     {
1210         debug(CollectionDList)
1211         {
1212             writefln("DList.opAssign: begin");
1213             scope(exit) writefln("DList.opAssign: end");
1214         }
1215 
1216         if (rhs._head !is null && _head is rhs._head)
1217         {
1218             return this;
1219         }
1220 
1221         if (rhs._head !is null)
1222         {
1223             rhs.addRef(rhs._head);
1224             debug(CollectionDList) writefln("DList.opAssign: Node %s has refcount: %s",
1225                     rhs._head._payload, *localPrefCount(rhs._head));
1226         }
1227 
1228         if (_head !is null)
1229         {
1230             Node *tmpNode = _head;
1231             delRef(tmpNode);
1232             if (tmpNode !is null
1233                 && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
1234             {
1235                 // If it was a single node list, only delRef must be used
1236                 // in order to avoid premature/double freeing
1237                 destroyUnused(tmpNode);
1238             }
1239         }
1240         _head = rhs._head;
1241         _allocator = rhs._allocator;
1242         return this;
1243     }
1244 
1245     ///
1246     static if (is(T == int))
1247     @safe unittest
1248     {
1249         import std.algorithm.comparison : equal;
1250 
1251         auto dl = DList!int(1);
1252         auto dl2 = DList!int(1, 2);
1253 
1254         dl = dl2; // this will free the old dl
1255         assert(equal(dl, [1, 2]));
1256         dl.front = 0;
1257         assert(equal(dl2, [0, 2]));
1258     }
1259 
1260     /**
1261      * Append the elements of `rhs` at the end of the list.
1262      *
1263      * If no allocator was provided when the list was created, the
1264      * $(REF, GCAllocator, std,experimental,allocator) will be used.
1265      *
1266      * Params:
1267      *      rhs = can be an element that is implicitly convertible to `T`, an
1268      *            input range of such elements, or another doubly linked list
1269      *
1270      * Returns:
1271      *      a reference to this list
1272      *
1273      * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`.
1274      */
1275     auto ref opOpAssign(string op, U)(auto ref U rhs)
1276         if (op == "~" &&
1277             (is (U == typeof(this))
1278              || is (U : T)
1279              || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
1280             ))
1281     {
1282         debug(CollectionDList)
1283         {
1284             writefln("DList.opOpAssign!~: %s begin", typeof(this).stringof);
1285             scope(exit) writefln("DList.opOpAssign!~: %s end", typeof(this).stringof);
1286         }
1287 
1288         insertBack(rhs);
1289         return this;
1290     }
1291 
1292     ///
1293     static if (is(T == int))
1294     @safe unittest
1295     {
1296         import std.algorithm.comparison : equal;
1297 
1298         auto d = DList!int(4, 5);
1299         DList!int dl;
1300         assert(dl.empty);
1301 
1302         dl ~= 1;
1303         dl ~= [2, 3];
1304         assert(equal(dl, [1, 2, 3]));
1305 
1306         // append an input range
1307         dl ~= d;
1308         assert(equal(dl, [1, 2, 3, 4, 5]));
1309         d.front = 0;
1310         assert(equal(dl, [1, 2, 3, 4, 5]));
1311     }
1312 
1313     /**
1314      * Remove the current element from the list. If there are no
1315      * more references to the current element, then it will be destroyed.
1316      */
1317     void remove()
1318     {
1319         debug(CollectionDList)
1320         {
1321             writefln("DList.remove: begin");
1322             scope(exit) writefln("DList.remove: end");
1323         }
1324         assert(!empty, "DList.remove: List is empty");
1325 
1326         Node *tmpNode = _head;
1327         _head = _head._next;
1328         if (_head !is null)
1329         {
1330             //addRef(_head);
1331             _head._prev = tmpNode._prev;
1332             delRef(tmpNode); // Remove tmpNode._next._prev ref
1333             tmpNode._next = null;
1334             //delRef(_head);
1335             if (tmpNode._prev !is null)
1336             {
1337                 addRef(_head);
1338                 tmpNode._prev._next = _head;
1339                 delRef(tmpNode); // Remove tmpNode._prev._next ref
1340                 tmpNode._prev = null;
1341             }
1342         }
1343         else if (tmpNode._prev !is null)
1344         {
1345             _head = tmpNode._prev;
1346             //addRef(_head);
1347             tmpNode._prev = null;
1348             //delRef(_head);
1349             _head._next = null;
1350             delRef(tmpNode);
1351         }
1352         delRef(tmpNode); // Remove old head ref
1353         if (tmpNode !is null
1354                 && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
1355         {
1356             // If it was a single node list, only delRef must be used
1357             // in order to avoid premature/double freeing
1358             destroyUnused(tmpNode);
1359         }
1360     }
1361 
1362     ///
1363     static if (is(T == int))
1364     @safe unittest
1365     {
1366         import std.algorithm.comparison : equal;
1367 
1368         auto dl = DList!int(1, 2, 3);
1369         auto dl2 = dl;
1370 
1371         assert(equal(dl, [1, 2, 3]));
1372         dl.popFront;
1373         dl.remove();
1374         assert(equal(dl, [3]));
1375         assert(equal(dl2, [1, 3]));
1376         dl.popPrev;
1377         assert(equal(dl, [1, 3]));
1378     }
1379 
1380     debug(CollectionDList)
1381     void printRefCount(Node *sn = null)
1382     {
1383         import std.stdio;
1384         writefln("DList.printRefCount: begin");
1385         scope(exit) writefln("DList.printRefCount: end");
1386 
1387         Node *tmpNode;
1388         if (sn is null)
1389             tmpNode = _head;
1390         else
1391             tmpNode = sn;
1392 
1393         while (tmpNode !is null && tmpNode._prev !is null)
1394         {
1395             // Rewind to the beginning of the list
1396             tmpNode = tmpNode._prev;
1397         }
1398         while (tmpNode !is null)
1399         {
1400             writefln("DList.printRefCount: Node %s has ref count %s",
1401                     tmpNode._payload, *localPrefCount(tmpNode));
1402             tmpNode = tmpNode._next;
1403         }
1404     }
1405 }
1406 
1407 version (unittest) private nothrow pure @safe
1408 void testInit(RCIAllocator allocator)
1409 {
1410     import std.algorithm.comparison : equal;
1411 
1412     DList!int dl = DList!int(allocator);
1413     assert(dl.empty);
1414     assert(dl.isUnique);
1415     int[] empty;
1416     assert(equal(dl, empty));
1417 
1418     DList!int dl2 = DList!int(allocator, 1);
1419     assert(equal(dl2, [1]));
1420 
1421     DList!int dl3 = DList!int(allocator, 1, 2);
1422     assert(equal(dl3, [1, 2]));
1423 
1424     DList!int dl4 = DList!int(allocator, [1]);
1425     assert(equal(dl4, [1]));
1426 
1427     DList!int dl5 = DList!int(allocator, [1, 2]);
1428     assert(equal(dl5, [1, 2]));
1429 }
1430 
1431 @safe unittest
1432 {
1433     import std.conv;
1434     SCAlloc statsCollectorAlloc;
1435     {
1436         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1437         () nothrow pure @safe {
1438             testInit(_allocator);
1439         }();
1440     }
1441     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1442     assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1443             ~ to!string(bytesUsed) ~ " bytes");
1444 }
1445 
1446 version (unittest) private nothrow pure @safe
1447 void testInsert(RCIAllocator allocator)
1448 {
1449     import std.algorithm.comparison : equal;
1450     import std.range.primitives : walkLength;
1451 
1452     DList!int dl = DList!int(allocator, 1);
1453     size_t pos = 0;
1454     dl.insert(pos, 2);
1455     assert(equal(dl, [2, 1]));
1456 
1457     DList!int dl2 = DList!int(allocator, 1);
1458     dl2.insert(pos, 2, 3);
1459     assert(equal(dl2, [2, 3, 1]));
1460 
1461     DList!int dl3 = DList!int(allocator, 1, 2);
1462     dl3.insert(pos, 3);
1463     assert(equal(dl3, [3, 1, 2]));
1464 
1465     DList!int dl4 = DList!int(allocator, 1, 2);
1466     dl4.insert(pos, 3, 4);
1467     assert(equal(dl4, [3, 4, 1, 2]));
1468 
1469     DList!int dl5 = DList!int(allocator, 1, 2);
1470     dl5.popFront();
1471     dl5.insert(pos, 3);
1472     assert(equal(dl5, [3, 2]));
1473     dl5.popPrev();
1474     assert(equal(dl5, [1, 3, 2]));
1475 
1476     DList!int dl6 = DList!int(allocator, 1, 2);
1477     dl6.popFront();
1478     dl6.insert(pos, 3, 4);
1479     assert(equal(dl6, [3, 4, 2]));
1480     dl6.popPrev();
1481     assert(equal(dl6, [1, 3, 4, 2]));
1482     dl6.insertBack(5);
1483     assert(equal(dl6, [1, 3, 4, 2, 5]));
1484     dl6.insertBack(6, 7);
1485     assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7]));
1486     dl6.insertBack([8]);
1487     assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8]));
1488     dl6.insertBack([9, 10]);
1489     assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10]));
1490     int[] empty;
1491     dl6.insertBack(empty);
1492     assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10]));
1493     dl6.insert(pos, empty);
1494     assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10]));
1495 
1496     DList!int dl7 = DList!int(allocator, 1);
1497     assert(equal(dl7, [1]));
1498     dl7.insert(pos, 2);
1499     assert(equal(dl7, [2, 1]));
1500     pos = walkLength(dl7);
1501     dl7.insert(pos, 3);
1502     assert(equal(dl7, [2, 1, 3]));
1503     dl7.insert(pos, 4);
1504     assert(equal(dl7, [2, 1, 4, 3]));
1505 }
1506 
1507 @safe unittest
1508 {
1509     import std.conv;
1510     SCAlloc statsCollectorAlloc;
1511     {
1512         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1513         () nothrow pure @safe {
1514             testInsert(_allocator);
1515         }();
1516     }
1517     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1518     assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1519             ~ to!string(bytesUsed) ~ " bytes");
1520 }
1521 
1522 version (unittest) private nothrow pure @safe
1523 void testRemove(RCIAllocator allocator)
1524 {
1525     import std.algorithm.comparison : equal;
1526 
1527     DList!int dl = DList!int(allocator, 1);
1528     size_t pos = 0;
1529     dl.remove();
1530     assert(dl.empty);
1531     assert(dl.isUnique);
1532     assert(!dl._allocator.isNull);
1533 
1534     dl.insert(pos, 2);
1535     auto dl2 = dl;
1536     auto dl3 = dl;
1537     assert(!dl.isUnique);
1538 
1539     dl.popFront();
1540     assert(dl.empty);
1541     assert(!dl._allocator.isNull);
1542 
1543     dl2.popPrev();
1544     assert(dl2.empty);
1545     assert(dl3.isUnique);
1546 
1547     auto dl4 = dl3;
1548     assert(!dl3.isUnique);
1549     dl4.remove();
1550     assert(dl4.empty);
1551     assert(!dl3.empty);
1552     assert(dl3.isUnique);
1553 }
1554 
1555 @safe unittest
1556 {
1557     import std.conv;
1558     SCAlloc statsCollectorAlloc;
1559     {
1560         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1561         () nothrow pure @safe {
1562             testRemove(_allocator);
1563         }();
1564     }
1565     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1566     assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1567             ~ to!string(bytesUsed) ~ " bytes");
1568 }
1569 
1570 version (unittest) private nothrow pure @safe
1571 void testCopyAndRef(RCIAllocator allocator)
1572 {
1573     import std.algorithm.comparison : equal;
1574 
1575     auto dlFromList = DList!int(allocator, 1, 2, 3);
1576     auto dlFromRange = DList!int(allocator, dlFromList);
1577     assert(equal(dlFromList, dlFromRange));
1578 
1579     dlFromList.popFront();
1580     assert(equal(dlFromList, [2, 3]));
1581     assert(equal(dlFromRange, [1, 2, 3]));
1582 
1583     DList!int dlInsFromRange = DList!int(allocator);
1584     size_t pos = 0;
1585     dlInsFromRange.insert(pos, dlFromList);
1586     dlFromList.popFront();
1587     assert(equal(dlFromList, [3]));
1588     assert(equal(dlInsFromRange, [2, 3]));
1589 
1590     DList!int dlInsBackFromRange = DList!int(allocator);
1591     dlInsBackFromRange.insert(pos, dlFromList);
1592     dlFromList.popFront();
1593     assert(dlFromList.empty);
1594     assert(equal(dlInsBackFromRange, [3]));
1595 
1596     auto dlFromRef = dlInsFromRange;
1597     auto dlFromDup = dlInsFromRange.dup;
1598     assert(dlInsFromRange.front == 2);
1599     dlFromRef.front = 5;
1600     assert(dlInsFromRange.front == 5);
1601     assert(dlFromDup.front == 2);
1602 }
1603 
1604 @safe unittest
1605 {
1606     import std.conv;
1607     SCAlloc statsCollectorAlloc;
1608     {
1609         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1610         () nothrow pure @safe {
1611             testCopyAndRef(_allocator);
1612         }();
1613     }
1614     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1615     assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1616             ~ to!string(bytesUsed) ~ " bytes");
1617 }
1618 
1619 @safe unittest
1620 {
1621     import std.algorithm.comparison : equal;
1622 
1623     SCAlloc statsCollectorAlloc;
1624     auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1625 
1626     DList!int dl = DList!int(_allocator, 1, 2, 3);
1627     auto before = statsCollectorAlloc.bytesUsed;
1628     {
1629         DList!int dl2 = dl;
1630         dl2.popFront();
1631         assert(equal(dl2, [2, 3]));
1632     }
1633     assert(before == statsCollectorAlloc.bytesUsed);
1634     assert(equal(dl, [1, 2, 3]));
1635     dl.tail();
1636 }
1637 
1638 version(unittest) private nothrow pure @safe
1639 void testImmutability(RCISharedAllocator allocator)
1640 {
1641     auto s = immutable DList!(int)(allocator, 1, 2, 3);
1642     auto s2 = s;
1643     auto s3 = s2.save();
1644 
1645     assert(s2.front == 1);
1646     static assert(!__traits(compiles, s2.front = 4));
1647     static assert(!__traits(compiles, s2.popFront()));
1648 
1649     auto s4 = s2.tail;
1650     assert(s4.front == 2);
1651     static assert(!__traits(compiles, s4 = s4.tail));
1652 }
1653 
1654 version(unittest) private nothrow pure @safe
1655 void testConstness(RCISharedAllocator allocator)
1656 {
1657     auto s = const DList!(int)(allocator, 1, 2, 3);
1658     auto s2 = s;
1659     auto s3 = s2.save();
1660 
1661     assert(s2.front == 1);
1662     static assert(!__traits(compiles, s2.front = 4));
1663     static assert(!__traits(compiles, s2.popFront()));
1664 
1665     auto s4 = s2.tail;
1666     assert(s4.front == 2);
1667     static assert(!__traits(compiles, s4 = s4.tail));
1668 }
1669 
1670 @safe unittest
1671 {
1672     import std.conv;
1673     import std.experimental.allocator : processAllocator;
1674     SCAlloc statsCollectorAlloc;
1675     {
1676         // TODO: StatsCollector need to be made shareable
1677         //auto _allocator = sharedAllocatorObject(&statsCollectorAlloc);
1678         () nothrow pure @safe {
1679             testConstness(processAllocatorObject());
1680             testImmutability(processAllocatorObject());
1681         }();
1682     }
1683     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1684     assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1685             ~ to!string(bytesUsed) ~ " bytes");
1686 }
1687 
1688 version(unittest) private nothrow pure @safe
1689 void testConcatAndAppend(RCIAllocator allocator)
1690 {
1691     import std.algorithm.comparison : equal;
1692 
1693     auto dl = DList!(int)(allocator, 1, 2, 3);
1694     DList!(int) dl2 = DList!int(allocator);
1695 
1696     auto dl3 = dl ~ dl2;
1697     assert(equal(dl3, [1, 2, 3]));
1698 
1699     auto dl4 = dl3;
1700     dl3 = dl3 ~ 4;
1701     assert(equal(dl3, [1, 2, 3, 4]));
1702     dl3 = dl3 ~ [5];
1703     assert(equal(dl3, [1, 2, 3, 4, 5]));
1704     assert(equal(dl4, [1, 2, 3]));
1705 
1706     dl4 = dl3;
1707     dl3 ~= 6;
1708     assert(equal(dl3, [1, 2, 3, 4, 5, 6]));
1709     dl3 ~= [7];
1710     assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7]));
1711     assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7]));
1712 
1713     dl3 ~= dl3;
1714     assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
1715     assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
1716 
1717     DList!int dl5 = DList!int(allocator);
1718     dl5 ~= [1, 2, 3];
1719     assert(equal(dl5, [1, 2, 3]));
1720 }
1721 
1722 @safe unittest
1723 {
1724     import std.conv;
1725     SCAlloc statsCollectorAlloc;
1726     {
1727         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1728         () nothrow pure @safe {
1729             testConcatAndAppend(_allocator);
1730         }();
1731     }
1732     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1733     assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1734             ~ to!string(bytesUsed) ~ " bytes");
1735 }
1736 
1737 version(unittest) private nothrow pure @safe
1738 void testAssign(RCIAllocator allocator)
1739 {
1740     import std.algorithm.comparison : equal;
1741 
1742     auto dl = DList!int(allocator, 1, 2, 3);
1743     assert(equal(dl, [1, 2, 3]));
1744     {
1745         auto dl2 = DList!int(allocator, 4, 5, 6);
1746         dl = dl2;
1747         assert(equal(dl, dl2));
1748     }
1749     assert(equal(dl, [4, 5, 6]));
1750     dl.popPrev();
1751     assert(dl.empty);
1752 }
1753 
1754 @safe unittest
1755 {
1756     import std.conv;
1757     SCAlloc statsCollectorAlloc;
1758     {
1759         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1760         () nothrow pure @safe {
1761             testAssign(_allocator);
1762         }();
1763     }
1764     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1765     assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1766             ~ to!string(bytesUsed) ~ " bytes");
1767 }
1768 
1769 version(unittest) private nothrow pure @safe
1770 void testWithStruct(RCIAllocator allocator, RCISharedAllocator sharedAlloc)
1771 {
1772     import std.algorithm.comparison : equal;
1773 
1774     auto list = DList!int(allocator, 1, 2, 3);
1775     {
1776         auto listOfLists = DList!(DList!int)(allocator, list);
1777         size_t pos = 0;
1778         assert(equal(listOfLists.front, [1, 2, 3]));
1779         listOfLists.front.front = 2;
1780         assert(equal(listOfLists.front, [2, 2, 3]));
1781         static assert(!__traits(compiles, listOfLists.insert(pos, 1)));
1782 
1783         auto immListOfLists = immutable DList!(DList!int)(sharedAlloc, list);
1784         assert(immListOfLists.front.front == 2);
1785         static assert(!__traits(compiles, immListOfLists.front.front = 2));
1786     }
1787     assert(equal(list, [2, 2, 3]));
1788 }
1789 
1790 @safe unittest
1791 {
1792     import std.conv;
1793     import std.experimental.allocator : processAllocator;
1794     SCAlloc statsCollectorAlloc;
1795     {
1796         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1797         () nothrow pure @safe {
1798             testWithStruct(_allocator, processAllocatorObject());
1799         }();
1800     }
1801     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1802     assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1803             ~ to!string(bytesUsed) ~ " bytes");
1804 }
1805 
1806 version(unittest) private nothrow pure @safe
1807 void testWithClass(RCIAllocator allocator)
1808 {
1809     class MyClass
1810     {
1811         int x;
1812         this(int x) { this.x = x; }
1813     }
1814 
1815     MyClass c = new MyClass(10);
1816     {
1817         auto dl = DList!MyClass(allocator, c);
1818         assert(dl.front.x == 10);
1819         assert(dl.front is c);
1820         dl.front.x = 20;
1821     }
1822     assert(c.x == 20);
1823 }
1824 
1825 @safe unittest
1826 {
1827     import std.conv;
1828     SCAlloc statsCollectorAlloc;
1829     {
1830         auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1831         () nothrow pure @safe {
1832             testWithClass(_allocator);
1833         }();
1834     }
1835     auto bytesUsed = statsCollectorAlloc.bytesUsed;
1836     assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1837             ~ to!string(bytesUsed) ~ " bytes");
1838 }