1 /**
2 This module implements a singly-linked list container.
3 It can be used as a stack.
4 
5 This module is a submodule of $(MREF std, container).
6 
7 Source: $(PHOBOSSRC std/container/slist.d)
8 
9 Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders.
10 
11 License: Distributed under the Boost Software License, Version 1.0.
12 (See accompanying file LICENSE_1_0.txt or copy at $(HTTP
13 boost.org/LICENSE_1_0.txt)).
14 
15 Authors: $(HTTP erdani.com, Andrei Alexandrescu)
16 
17 $(SCRIPT inhibitQuickIndex = 1;)
18 */
19 module std.container.slist;
20 
21 ///
22 @safe unittest
23 {
24     import std.algorithm.comparison : equal;
25     import std.container : SList;
26 
27     auto s = SList!int(1, 2, 3);
28     assert(equal(s[], [1, 2, 3]));
29 
30     s.removeFront();
31     assert(equal(s[], [2, 3]));
32 
33     s.insertFront([5, 6]);
34     assert(equal(s[], [5, 6, 2, 3]));
35 
36     // If you want to apply range operations, simply slice it.
37     import std.algorithm.searching : countUntil;
38     import std.range : popFrontN, walkLength;
39 
40     auto sl = SList!int(1, 2, 3, 4, 5);
41     assert(countUntil(sl[], 2) == 1);
42 
43     auto r = sl[];
44     popFrontN(r, 2);
45     assert(walkLength(r) == 3);
46 }
47 
48 public import std.container.util;
49 
50 /**
51    Implements a simple and fast singly-linked list.
52    It can be used as a stack.
53 
54    `SList` uses reference semantics.
55  */
56 struct SList(T)
57 if (!is(T == shared))
58 {
59     import std.exception : enforce;
60     import std.range : Take;
61     import std.range.primitives : isInputRange, isForwardRange, ElementType;
62     import std.traits : isImplicitlyConvertible;
63 
64     private struct Node
65     {
66         Node * _next;
67         T _payload;
68     }
69     private struct NodeWithoutPayload
70     {
71         Node* _next;
72     }
73     static assert(NodeWithoutPayload._next.offsetof == Node._next.offsetof);
74 
75     private Node * _root;
76 
77     private void initialize() @trusted nothrow pure
78     {
79         if (_root) return;
80         _root = cast (Node*) new NodeWithoutPayload();
81     }
82 
83     private ref inout(Node*) _first() @property @safe nothrow pure inout
84     {
85         assert(_root, "root pointer must not be null");
86         return _root._next;
87     }
88 
89     private static Node * findLastNode(Node * n)
90     {
91         assert(n, "Node n pointer must not be null");
92         auto ahead = n._next;
93         while (ahead)
94         {
95             n = ahead;
96             ahead = n._next;
97         }
98         return n;
99     }
100 
101     private static Node * findLastNode(Node * n, size_t limit)
102     {
103         assert(n, "Node n pointer must not be null");
104         assert(limit, "limit must be greater than 0");
105         auto ahead = n._next;
106         while (ahead)
107         {
108             if (!--limit) break;
109             n = ahead;
110             ahead = n._next;
111         }
112         return n;
113     }
114 
115     private static Node * findNode(Node * n, Node * findMe)
116     {
117         assert(n, "Node n pointer must not be null");
118         auto ahead = n._next;
119         while (ahead != findMe)
120         {
121             n = ahead;
122             enforce(n);
123             ahead = n._next;
124         }
125         return n;
126     }
127 
128     private static Node* findNodeByValue(Node* n, T value)
129     {
130         if (!n) return null;
131         auto ahead = n._next;
132         while (ahead && ahead._payload != value)
133         {
134             n = ahead;
135             ahead = n._next;
136         }
137         return n;
138     }
139 
140     private static auto createNodeChain(Stuff)(Stuff stuff)
141     if (isImplicitlyConvertible!(Stuff, T))
142     {
143         import std.range : only;
144         return createNodeChain(only(stuff));
145     }
146 
147     private static auto createNodeChain(Stuff)(Stuff stuff)
148     if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
149     {
150         static struct Chain
151         {
152             Node* first;
153             Node* last;
154             size_t length;
155         }
156 
157         Chain ch;
158 
159         foreach (item; stuff)
160         {
161             auto newNode = new Node(null, item);
162             (ch.first ? ch.last._next : ch.first) = newNode;
163             ch.last = newNode;
164             ++ch.length;
165         }
166 
167         return ch;
168     }
169 
170     private static size_t insertAfterNode(Stuff)(Node* n, Stuff stuff)
171     {
172         auto ch = createNodeChain(stuff);
173 
174         if (!ch.length) return 0;
175 
176         ch.last._next = n._next;
177         n._next = ch.first;
178 
179         return ch.length;
180     }
181 
182 /**
183 Constructor taking a number of nodes
184      */
185     this(U)(U[] values...)
186     if (isImplicitlyConvertible!(U, T))
187     {
188         insertFront(values);
189     }
190 
191 /**
192 Constructor taking an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
193      */
194     this(Stuff)(Stuff stuff)
195     if (isInputRange!Stuff
196             && isImplicitlyConvertible!(ElementType!Stuff, T)
197             && !is(Stuff == T[]))
198     {
199         insertFront(stuff);
200     }
201 
202 /**
203 Comparison for equality.
204 
205 Complexity: $(BIGOH min(n, n1)) where `n1` is the number of
206 elements in `rhs`.
207      */
208     bool opEquals(const SList rhs) const
209     {
210         return opEquals(rhs);
211     }
212 
213     /// ditto
214     bool opEquals(ref const SList rhs) const
215     {
216         if (_root is rhs._root) return true;
217         if (_root is null) return rhs._root is null || rhs._first is null;
218         if (rhs._root is null) return _root is null || _first is null;
219 
220         const(Node) * n1 = _first, n2 = rhs._first;
221 
222         for (;; n1 = n1._next, n2 = n2._next)
223         {
224             if (!n1) return !n2;
225             if (!n2 || n1._payload != n2._payload) return false;
226         }
227     }
228 
229 /**
230 Defines the container's primary range, which embodies a forward range.
231      */
232     struct Range
233     {
234         private Node * _head;
235         private this(Node * p) { _head = p; }
236 
237         /// Input range primitives.
238         @property bool empty() const { return !_head; }
239 
240         /// ditto
241         @property ref T front()
242         {
243             assert(!empty, "SList.Range.front: Range is empty");
244             return _head._payload;
245         }
246 
247         /// ditto
248         void popFront()
249         {
250             assert(!empty, "SList.Range.popFront: Range is empty");
251             _head = _head._next;
252         }
253 
254         /// Forward range primitive.
255         @property Range save() { return this; }
256 
257         T moveFront()
258         {
259             import std.algorithm.mutation : move;
260 
261             assert(!empty, "SList.Range.moveFront: Range is empty");
262             return move(_head._payload);
263         }
264 
265         bool sameHead(Range rhs)
266         {
267             return _head && _head == rhs._head;
268         }
269     }
270 
271     @safe unittest
272     {
273         static assert(isForwardRange!Range);
274     }
275 
276 /**
277 Property returning `true` if and only if the container has no
278 elements.
279 
280 Complexity: $(BIGOH 1)
281      */
282     @property bool empty() const
283     {
284         return _root is null || _first is null;
285     }
286 
287 /**
288 Duplicates the container. The elements themselves are not transitively
289 duplicated.
290 
291 Complexity: $(BIGOH n).
292      */
293     @property SList dup()
294     {
295         return SList(this[]);
296     }
297 
298 /**
299 Returns a range that iterates over all elements of the container, in
300 forward order.
301 
302 Complexity: $(BIGOH 1)
303      */
304     Range opSlice()
305     {
306         if (empty)
307             return Range(null);
308         else
309             return Range(_first);
310     }
311 
312 /**
313 Forward to `opSlice().front`.
314 
315 Complexity: $(BIGOH 1)
316      */
317     @property ref T front()
318     {
319         assert(!empty, "SList.front: List is empty");
320         return _first._payload;
321     }
322 
323     @safe unittest
324     {
325         auto s = SList!int(1, 2, 3);
326         s.front = 42;
327         assert(s == SList!int(42, 2, 3));
328     }
329 
330 /**
331 Returns a new `SList` that's the concatenation of `this` and its
332 argument. `opBinaryRight` is only defined if `Stuff` does not
333 define `opBinary`.
334      */
335     SList opBinary(string op, Stuff)(Stuff rhs)
336     if (op == "~" && is(typeof(SList(rhs))))
337     {
338         import std.range : chain, only;
339 
340         static if (isInputRange!Stuff)
341             alias r = rhs;
342         else
343             auto r = only(rhs);
344 
345         return SList(this[].chain(r));
346     }
347 
348     /// ditto
349     SList opBinaryRight(string op, Stuff)(Stuff lhs)
350     if (op == "~" && !is(typeof(lhs.opBinary!"~"(this))) && is(typeof(SList(lhs))))
351     {
352         import std.range : chain, only;
353 
354         static if (isInputRange!Stuff)
355             alias r = lhs;
356         else
357             auto r = only(lhs);
358 
359         return SList(r.chain(this[]));
360     }
361 
362 /**
363 Removes all contents from the `SList`.
364 
365 Postcondition: `empty`
366 
367 Complexity: $(BIGOH 1)
368      */
369     void clear()
370     {
371         if (_root)
372             _first = null;
373     }
374 
375 /**
376 Reverses SList in-place. Performs no memory allocation.
377 
378 Complexity: $(BIGOH n)
379      */
380     void reverse()
381     {
382         if (!empty)
383         {
384             Node* prev;
385             while (_first)
386             {
387                 auto next = _first._next;
388                 _first._next = prev;
389                 prev = _first;
390                 _first = next;
391             }
392             _first = prev;
393         }
394     }
395 
396 /**
397 Inserts `stuff` to the front of the container. `stuff` can be a
398 value convertible to `T` or a range of objects convertible to $(D
399 T). The stable version behaves the same, but guarantees that ranges
400 iterating over the container are never invalidated.
401 
402 Returns: The number of elements inserted
403 
404 Complexity: $(BIGOH m), where `m` is the length of `stuff`
405      */
406     size_t insertFront(Stuff)(Stuff stuff)
407     if (isInputRange!Stuff || isImplicitlyConvertible!(Stuff, T))
408     {
409         initialize();
410         return insertAfterNode(_root, stuff);
411     }
412 
413     /// ditto
414     alias insert = insertFront;
415 
416     /// ditto
417     alias stableInsert = insert;
418 
419     /// ditto
420     alias stableInsertFront = insertFront;
421 
422 /**
423 Picks one value in an unspecified position in the container, removes
424 it from the container, and returns it. The stable version behaves the same,
425 but guarantees that ranges iterating over the container are never invalidated.
426 
427 Precondition: `!empty`
428 
429 Returns: The element removed.
430 
431 Complexity: $(BIGOH 1).
432      */
433     T removeAny()
434     {
435         import std.algorithm.mutation : move;
436 
437         assert(!empty, "SList.removeAny: List is empty");
438         auto result = move(_first._payload);
439         _first = _first._next;
440         return result;
441     }
442     /// ditto
443     alias stableRemoveAny = removeAny;
444 
445 /**
446 Removes the value at the front of the container. The stable version
447 behaves the same, but guarantees that ranges iterating over the
448 container are never invalidated.
449 
450 Precondition: `!empty`
451 
452 Complexity: $(BIGOH 1).
453      */
454     void removeFront()
455     {
456         assert(!empty, "SList.removeFront: List is empty");
457         _first = _first._next;
458     }
459 
460     /// ditto
461     alias stableRemoveFront = removeFront;
462 
463 /**
464 Removes `howMany` values at the front or back of the
465 container. Unlike the unparameterized versions above, these functions
466 do not throw if they could not remove `howMany` elements. Instead,
467 if $(D howMany > n), all elements are removed. The returned value is
468 the effective number of elements removed. The stable version behaves
469 the same, but guarantees that ranges iterating over the container are
470 never invalidated.
471 
472 Returns: The number of elements removed
473 
474 Complexity: $(BIGOH howMany * log(n)).
475      */
476     size_t removeFront(size_t howMany)
477     {
478         size_t result;
479         while (_first && result < howMany)
480         {
481             _first = _first._next;
482             ++result;
483         }
484         return result;
485     }
486 
487     /// ditto
488     alias stableRemoveFront = removeFront;
489 
490 /**
491 Inserts `stuff` after range `r`, which must be a range
492 previously extracted from this container. Given that all ranges for a
493 list end at the end of the list, this function essentially appends to
494 the list and uses `r` as a potentially fast way to reach the last
495 node in the list. Ideally `r` is positioned near or at the last
496 element of the list.
497 
498 `stuff` can be a value convertible to `T` or a range of objects
499 convertible to `T`. The stable version behaves the same, but
500 guarantees that ranges iterating over the container are never
501 invalidated.
502 
503 Returns: The number of values inserted.
504 
505 Complexity: $(BIGOH k + m), where `k` is the number of elements in
506 `r` and `m` is the length of `stuff`.
507 
508 Example:
509 --------------------
510 auto sl = SList!string(["a", "b", "d"]);
511 sl.insertAfter(sl[], "e"); // insert at the end (slowest)
512 assert(std.algorithm.equal(sl[], ["a", "b", "d", "e"]));
513 sl.insertAfter(std.range.take(sl[], 2), "c"); // insert after "b"
514 assert(std.algorithm.equal(sl[], ["a", "b", "c", "d", "e"]));
515 --------------------
516      */
517 
518     size_t insertAfter(Stuff)(Range r, Stuff stuff)
519     if (isInputRange!Stuff || isImplicitlyConvertible!(Stuff, T))
520     {
521         initialize();
522         if (!_first)
523         {
524             enforce(!r._head);
525             return insertFront(stuff);
526         }
527         enforce(r._head);
528         auto n = findLastNode(r._head);
529         return insertAfterNode(n, stuff);
530     }
531 
532 /**
533 Similar to `insertAfter` above, but accepts a range bounded in
534 count. This is important for ensuring fast insertions in the middle of
535 the list.  For fast insertions after a specified position `r`, use
536 $(D insertAfter(take(r, 1), stuff)). The complexity of that operation
537 only depends on the number of elements in `stuff`.
538 
539 Precondition: $(D r.original.empty || r.maxLength > 0)
540 
541 Returns: The number of values inserted.
542 
543 Complexity: $(BIGOH k + m), where `k` is the number of elements in
544 `r` and `m` is the length of `stuff`.
545      */
546     size_t insertAfter(Stuff)(Take!Range r, Stuff stuff)
547     if (isInputRange!Stuff || isImplicitlyConvertible!(Stuff, T))
548     {
549         auto orig = r.source;
550         if (!orig._head)
551         {
552             // Inserting after a null range counts as insertion to the
553             // front
554             return insertFront(stuff);
555         }
556         enforce(!r.empty);
557         // Find the last valid element in the range
558         foreach (i; 1 .. r.maxLength)
559         {
560             if (!orig._head._next) break;
561             orig.popFront();
562         }
563         // insert here
564         return insertAfterNode(orig._head, stuff);
565     }
566 
567 /// ditto
568     alias stableInsertAfter = insertAfter;
569 
570 /**
571 Removes a range from the list in linear time.
572 
573 Returns: An empty range.
574 
575 Complexity: $(BIGOH n)
576      */
577     Range linearRemove(Range r)
578     {
579         if (!_first)
580         {
581             enforce(!r._head);
582             return this[];
583         }
584         auto n = findNode(_root, r._head);
585         n._next = null;
586         return Range(null);
587     }
588 
589 /**
590 Removes a `Take!Range` from the list in linear time.
591 
592 Returns: A range comprehending the elements after the removed range.
593 
594 Complexity: $(BIGOH n)
595      */
596     Range linearRemove(Take!Range r)
597     {
598         auto orig = r.source;
599         // We have something to remove here
600         if (orig._head == _first)
601         {
602             // remove straight from the head of the list
603             for (; !r.empty; r.popFront())
604             {
605                 removeFront();
606             }
607             return this[];
608         }
609         if (!r.maxLength)
610         {
611             // Nothing to remove, return the range itself
612             return orig;
613         }
614         // Remove from somewhere in the middle of the list
615         enforce(_first);
616         auto n1 = findNode(_root, orig._head);
617         auto n2 = findLastNode(orig._head, r.maxLength);
618         n1._next = n2._next;
619         return Range(n1._next);
620     }
621 
622 /// ditto
623     alias stableLinearRemove = linearRemove;
624 
625 /**
626 Removes the first occurence of an element from the list in linear time.
627 
628 Returns: True if the element existed and was successfully removed, false otherwise.
629 
630 Params:
631     value = value of the node to be removed
632 
633 Complexity: $(BIGOH n)
634      */
635     bool linearRemoveElement(T value)
636     {
637         auto n1 = findNodeByValue(_root, value);
638 
639         if (n1 && n1._next)
640         {
641             n1._next = n1._next._next;
642             return true;
643         }
644 
645         return false;
646     }
647 }
648 
649 @safe unittest
650 {
651     import std.algorithm.comparison : equal;
652 
653     auto e = SList!int();
654     auto b = e.linearRemoveElement(2);
655     assert(b == false);
656     assert(e.empty());
657     auto a = SList!int(-1, 1, 2, 1, 3, 4);
658     b = a.linearRemoveElement(1);
659     assert(equal(a[], [-1, 2, 1, 3, 4]));
660     assert(b == true);
661     b = a.linearRemoveElement(-1);
662     assert(b == true);
663     assert(equal(a[], [2, 1, 3, 4]));
664     b = a.linearRemoveElement(1);
665     assert(b == true);
666     assert(equal(a[], [2, 3, 4]));
667     b = a.linearRemoveElement(2);
668     assert(b == true);
669     b = a.linearRemoveElement(20);
670     assert(b == false);
671     assert(equal(a[], [3, 4]));
672     b = a.linearRemoveElement(4);
673     assert(b == true);
674     assert(equal(a[], [3]));
675     b = a.linearRemoveElement(3);
676     assert(b == true);
677     assert(a.empty());
678     a.linearRemoveElement(3);
679 }
680 
681 @safe unittest
682 {
683     import std.algorithm.comparison : equal;
684 
685     auto a = SList!int(5);
686     auto b = a;
687     auto r = a[];
688     a.insertFront(1);
689     b.insertFront(2);
690     assert(equal(a[], [2, 1, 5]));
691     assert(equal(b[], [2, 1, 5]));
692     r.front = 9;
693     assert(equal(a[], [2, 1, 9]));
694     assert(equal(b[], [2, 1, 9]));
695 }
696 
697 @safe unittest
698 {
699     auto s = SList!int(1, 2, 3);
700     auto n = s.findLastNode(s._root);
701     assert(n && n._payload == 3);
702 }
703 
704 @safe unittest
705 {
706     import std.range.primitives;
707     auto s = SList!int(1, 2, 5, 10);
708     assert(walkLength(s[]) == 4);
709 }
710 
711 @safe unittest
712 {
713     import std.range : take;
714     auto src = take([0, 1, 2, 3], 3);
715     auto s = SList!int(src);
716     assert(s == SList!int(0, 1, 2));
717 }
718 
719 @safe unittest
720 {
721     auto a = SList!int();
722     auto b = SList!int();
723     auto c = a ~ b[];
724     assert(c.empty);
725 }
726 
727 @safe unittest
728 {
729     auto a = SList!int(1, 2, 3);
730     auto b = SList!int(4, 5, 6);
731     auto c = a ~ b[];
732     assert(c == SList!int(1, 2, 3, 4, 5, 6));
733 }
734 
735 @safe unittest
736 {
737     auto a = SList!int(1, 2, 3);
738     auto b = [4, 5, 6];
739     auto c = a ~ b;
740     assert(c == SList!int(1, 2, 3, 4, 5, 6));
741 }
742 
743 @safe unittest
744 {
745     auto a = SList!int(1, 2, 3);
746     auto c = a ~ 4;
747     assert(c == SList!int(1, 2, 3, 4));
748 }
749 
750 @safe unittest
751 {
752     auto a = SList!int(2, 3, 4);
753     auto b = 1 ~ a;
754     assert(b == SList!int(1, 2, 3, 4));
755 }
756 
757 @safe unittest
758 {
759     auto a = [1, 2, 3];
760     auto b = SList!int(4, 5, 6);
761     auto c = a ~ b;
762     assert(c == SList!int(1, 2, 3, 4, 5, 6));
763 }
764 
765 @safe unittest
766 {
767     auto s = SList!int(1, 2, 3, 4);
768     s.insertFront([ 42, 43 ]);
769     assert(s == SList!int(42, 43, 1, 2, 3, 4));
770 }
771 
772 @safe unittest
773 {
774     auto s = SList!int(1, 2, 3);
775     assert(s.removeAny() == 1);
776     assert(s == SList!int(2, 3));
777     assert(s.stableRemoveAny() == 2);
778     assert(s == SList!int(3));
779 }
780 
781 @safe unittest
782 {
783     import std.algorithm.comparison : equal;
784 
785     auto s = SList!int(1, 2, 3);
786     s.removeFront();
787     assert(equal(s[], [2, 3]));
788     s.stableRemoveFront();
789     assert(equal(s[], [3]));
790 }
791 
792 @safe unittest
793 {
794     auto s = SList!int(1, 2, 3, 4, 5, 6, 7);
795     assert(s.removeFront(3) == 3);
796     assert(s == SList!int(4, 5, 6, 7));
797 }
798 
799 @safe unittest
800 {
801     auto a = SList!int(1, 2, 3);
802     auto b = SList!int(1, 2, 3);
803     assert(a.insertAfter(a[], b[]) == 3);
804 }
805 
806 @safe unittest
807 {
808     import std.range : take;
809     auto s = SList!int(1, 2, 3, 4);
810     auto r = take(s[], 2);
811     assert(s.insertAfter(r, 5) == 1);
812     assert(s == SList!int(1, 2, 5, 3, 4));
813 }
814 
815 @safe unittest
816 {
817     import std.algorithm.comparison : equal;
818     import std.range : take;
819 
820     // insertAfter documentation example
821     auto sl = SList!string(["a", "b", "d"]);
822     sl.insertAfter(sl[], "e"); // insert at the end (slowest)
823     assert(equal(sl[], ["a", "b", "d", "e"]));
824     sl.insertAfter(take(sl[], 2), "c"); // insert after "b"
825     assert(equal(sl[], ["a", "b", "c", "d", "e"]));
826 }
827 
828 @safe unittest
829 {
830     import std.range.primitives;
831     auto s = SList!int(1, 2, 3, 4, 5);
832     auto r = s[];
833     popFrontN(r, 3);
834     auto r1 = s.linearRemove(r);
835     assert(s == SList!int(1, 2, 3));
836     assert(r1.empty);
837 }
838 
839 @safe unittest
840 {
841     auto s = SList!int(1, 2, 3, 4, 5);
842     auto r = s[];
843     auto r1 = s.linearRemove(r);
844     assert(s == SList!int());
845     assert(r1.empty);
846 }
847 
848 @safe unittest
849 {
850     import std.algorithm.comparison : equal;
851     import std.range;
852 
853     auto s = SList!int(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
854     auto r = s[];
855     popFrontN(r, 3);
856     auto r1 = take(r, 4);
857     assert(equal(r1, [4, 5, 6, 7]));
858     auto r2 = s.linearRemove(r1);
859     assert(s == SList!int(1, 2, 3, 8, 9, 10));
860     assert(equal(r2, [8, 9, 10]));
861 }
862 
863 @safe unittest
864 {
865     import std.range.primitives;
866     auto lst = SList!int(1, 5, 42, 9);
867     assert(!lst.empty);
868     assert(lst.front == 1);
869     assert(walkLength(lst[]) == 4);
870 
871     auto lst2 = lst ~ [ 1, 2, 3 ];
872     assert(walkLength(lst2[]) == 7);
873 
874     auto lst3 = lst ~ [ 7 ];
875     assert(walkLength(lst3[]) == 5);
876 }
877 
878 @safe unittest
879 {
880     auto s = make!(SList!int)(1, 2, 3);
881 }
882 
883 // https://issues.dlang.org/show_bug.cgi?id=5193
884 @safe unittest
885 {
886     static struct Data
887     {
888         const int val;
889     }
890     SList!Data list;
891 }
892 
893 @safe unittest
894 {
895     auto s = SList!int([1, 2, 3]);
896     s.front = 5; //test frontAssign
897     assert(s.front == 5);
898     auto r = s[];
899     r.front = 1; //test frontAssign
900     assert(r.front == 1);
901 }
902 
903 // https://issues.dlang.org/show_bug.cgi?id=14920
904 @safe unittest
905 {
906     SList!int s;
907     s.insertAfter(s[], 1);
908     assert(s.front == 1);
909 }
910 
911 // https://issues.dlang.org/show_bug.cgi?id=15659
912 @safe unittest
913 {
914     SList!int s;
915     s.clear();
916 }
917 
918 @safe unittest
919 {
920     SList!int s;
921     s.reverse();
922 }
923 
924 @safe unittest
925 {
926     import std.algorithm.comparison : equal;
927 
928     auto s = SList!int([1, 2, 3]);
929     assert(s[].equal([1, 2, 3]));
930 
931     s.reverse();
932     assert(s[].equal([3, 2, 1]));
933 }
934 
935 @safe unittest
936 {
937     auto s = SList!int([4, 6, 8, 12, 16]);
938     auto d = s.dup;
939     assert(d !is s);
940     assert(d == s);
941 }