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 }