libbmb
Modern implementation of STL
Loading...
Searching...
No Matches
linked_list.h
Go to the documentation of this file.
1#pragma once
8#include <cstddef>
9#include <initializer_list>
10#include <stdexcept>
11
12#include "algo/algo_base.h"
14#include "details/llist_nodes.h"
15#include "utils/allocator.h"
16#include "utils/compare.h"
17#include "utils/iterators.h"
18#include "utils/move.h"
19#include "utils/type_traits.h"
20
21namespace bmb {
22
50template <typename T,
51 bool TrackSize = true,
52 bool TrackLast = true, typename Allocator = PrimitiveAllocator>
54 // TODO: refactor iterator/pointers usage
55
56 using Node = llist::Node<T>;
58
60
61 // Used for optional fields. See the end of the definition.
62 struct Empty {};
63 // [[no_unique_address]] must differentiate
64 // fileds of the same type. In this case it is not needed because wastes memory
65 struct Empty2 {};
66
67public:
68 using value_type = T;
72 using const_pointer = const value_type*;
73 using size_type = size_t;
74 using allocator_type = Allocator;
77
78 LinkedList() noexcept(noexcept(Allocator())) {
79 initOptionalFields();
80 }
81
82 explicit LinkedList(const Allocator& alloc)
83 : alloc_(alloc) {
84 initOptionalFields();
85 }
86
100 explicit LinkedList(size_t count, const value_type& value = value_type(),
101 const Allocator& alloc = Allocator())
102 : LinkedList(alloc) {
103 // Thanks to delegeting c-tor, if exception is thrown,
104 // the destructor will be called automatically
105 auto cur_it = cbeforeBegin();
106 for (; count > 0; --count, ++cur_it) {
107 emplaceAfter(cur_it, value);
108 }
109 }
110
123 template <InputIterator Iter>
124 LinkedList(Iter first, Iter last,
125 const Allocator& alloc = Allocator())
126 : LinkedList(alloc) {
127 // Thanks to delegeting c-tor, if exception is thrown,
128 // the destructor will be called automatically
129 auto cur = beforeBegin();
130 for (; first != last; ++first) {
131 emplaceAfter(cur, *first);
132 ++cur;
133 }
134 }
135
144 LinkedList(std::initializer_list<value_type> init_list,
145 const Allocator& alloc = Allocator())
146 : LinkedList(init_list.begin(), init_list.end(), alloc) {}
147
153 LinkedList(const LinkedList& other)
154 : LinkedList(other.cbegin(),
155 other.cend(), other.alloc_) {}
156
161 LinkedList(LinkedList&& other) noexcept(noexcept(Allocator(move(other.alloc_))))
162 : head_(other.head_)
163 , size_(other.size_)
164 , last_(other.last_)
165 , alloc_(move(other.alloc_)) {
166 other.head_.next = nullptr;
167 if constexpr (TrackSize) other.size_ = 0;
168 if constexpr (TrackLast) other.last_ = &other.head_;
169 }
170
183 swap(other);
184 return *this;
185 }
186
188
198 void swap(LinkedList& other) {
199 using bmb::swap;
200 // First, swap allocs to provide strong safety
201 swap(alloc_, other.alloc_);
202
203 // Head never points to itself: either node in heap or nullptr,
204 // so it is safe just swap it.
205 swap(head_, other.head_);
206 if constexpr (TrackSize) swap(size_, other.size_);
207 if constexpr (TrackLast) swap(last_, other.last_);
208 }
209
222 void clear() noexcept {
224 }
225
242 return emplaceAfter(pos, move(value));
243 }
244
260 iterator insertAfter(const_iterator pos, std::initializer_list<T> init_list) {
261 return insertAfter(pos, init_list.begin(), init_list.end());
262 }
263
286 template <InputIterator Iter>
287 iterator insertAfter(const_iterator pos, Iter first, Iter last) {
288 // Constructor provides strong exception guarantee.
289 // If we used emplaceAfter(...) for each iterator [first, last),
290 // it would be more complex to provide exception guarantee here.
291 LinkedList tmp(first, last, getAllocator());
292
293 if (tmp.isEmpty()) return pos.constCast();
294
295 return spliceAfter(pos, move(tmp));
296 }
297
319 template <typename... Args>
320 iterator emplaceAfter(const_iterator pos, Args&&... args) {
321 Node* new_node = createNode(forward<Args>(args)...);
322 BaseNode* before_new = pos.node_;
323
324 // Order of operations does matter
327
328 if constexpr (TrackSize) ++size_;
329
330 if constexpr (TrackLast) {
331 if (isIterToLast(pos)) last_ = new_node;
332 }
333
334 return iterator(new_node);
335 }
336
354 template <typename... Args>
358
378 template <typename... Args>
380 return *emplaceAfter(iterator(getLastNode()),
381 forward<Args>(args)...);
382 }
383
400 BaseNode* to_destroy = pos.node_->next;
401
402 if (to_destroy == nullptr) return pos.constCast();
403
405
406 pos.node_->next = after_destroyed;
407
408 destroyNode(castToNode(to_destroy));
409
410 if constexpr (TrackSize) --size_;
411
412 if constexpr (TrackLast) {
413 // Now `pos` becomes the last element in the list
414 if (last_ == to_destroy) last_ = pos.node_;
415 }
416
418 }
419
441 if (first == last) return last.constCast();
442
443 while (next(first) != last) {
445 }
446 return last.constCast();
447 }
448
458
475 // Make sure list is not empty.
476 if (begin() == end()) return;
477
478 // Find before the last
479 BaseNode* before_last = &head_;
480 while (before_last->next->next != nullptr) before_last = before_last->next;
481
483 }
484
486 template <typename Cmp = less>
488 merge(move(other), move(cmp));
489 }
490
519 template <typename Cmp = less>
521 // NOTE: If you want, you can try to make merge at least
522 // basic exception safety. Just save the last node when
523 // node should be linked to the node from other list.
524 // If there is exception, link these "lost" nodes to the
525 // end of any list - they will be destroyed by destructor.
526 //
527 // Also if you may make merge be stable.
528
529 if (this == &other) return;
530
531 if (getAllocator() != other.getAllocator())
532 throw std::runtime_error(
533 "Try to merge linked list "
534 "allocated via different allocator");
535
536 // Just to make comparasion less verbose
537 auto compare = [this, &cmp](auto a, auto b) -> bool {
538 return cmp(castToNode(a)->val, castToNode(b)->val);
539 };
540
541 auto a = head_.next;
542 auto b = other.head_.next;
543 auto prev = &head_;
544
545 while (a && b) {
546 // Make `a` always less than `b`
547 if (!compare(a, b)) bmb::swap(a, b);
548
549 prev->next = a;
550 prev = a;
551 a = a->next;
552 }
553 prev->next = a != nullptr ? a : b;
554
555 other.head_.next = nullptr;
556
557 if constexpr (TrackSize) {
558 size_ += other.size_;
559 other.size_ = 0;
560 }
561
562 if constexpr (TrackLast) {
563 // If nodes from `other` > than from `this`,
564 // they will be placed after `last_`, so update it.
565 // Remember to handle cases with empty lists.
566
567 // `this` is empty, `other` is not
568 if (last_ == &head_
569 && other.last_ != &other.head_) last_ = other.last_;
570 // `this` is not empty, `other` is not empty,
571 // and nodes from `other` are after `this->last_`
572 else if (last_ != &head_
573 && other.last_ != &other.head_
574 && compare(last_, other.last_)) {
575 last_ = other.last_;
576 }
577
578 other.last_ = &other.head_;
579 }
580 }
581
586
613 if (this == &other || other.isEmpty()) return pos.constCast();
614
615 if (getAllocator() != other.getAllocator())
616 throw std::runtime_error(
617 "Try to transfer linked list "
618 "allocated via different allocator");
619
620 // If track last, we can bypass its search.
621 if constexpr (TrackLast) {
622 auto last_transferred = other.last_;
623
624 transferAfter(pos.node_,
625 &other.head_, other.last_);
626
627 if (isIterToLast(pos)) last_ = other.last_;
628
629 other.last_ = &other.head_;
630
631 // Update size
632 if constexpr (TrackSize) {
633 size_ += other.size_;
634 other.size_ = 0;
635 }
636
638 }
639
640 // If `last_` is not available, use non effective version
641 return spliceAfter(pos, move(other),
642 other.beforeBegin(), other.end());
643 }
644
650
682 if (getAllocator() != other.getAllocator())
683 throw std::runtime_error(
684 "Try to transfer linked list nodes "
685 "allocated via different allocator");
686
687 // If no elements to transfer
688 if (first == last
689 || next(first) == last) return pos.constCast();
690
691 // Find last element to transfer(right before `last` iterator)
692 auto before = first.node_;
693 auto end = before;
694 size_t cnt = 0;
695
696 while (end->next != last.node_) {
697 end = end->next;
698 ++cnt;
699 }
700
701 if constexpr (TrackSize) {
702 // Update only if obtained new elements
703 if (this != &other) {
704 size_ += cnt;
705 other.size_ -= cnt;
706 }
707 }
708
709 if constexpr (TrackLast) {
710 if (isIterToLast(pos)) last_ = end;
711 }
712
713 return iterator(transferAfter(pos.node_, before, end));
714 }
715
726 BaseNode* left = nullptr;
727 BaseNode* right = head_.next;
728 // Left will be point to the new first element
729 while (right != nullptr) {
730 auto keep = right->next;
731 right->next = left;
732 left = right;
733 right = keep;
734 }
735
736 if constexpr (TrackLast) {
737 if (!isEmpty()) last_ = head_.next;
738 // If empty - last points to the `head`
739 }
740
741 head_.next = left;
742 }
743
749 reference front() noexcept { return *begin(); }
750
757
768 return *iterator(getLastNode());
769 }
770
781 return *iterator(getLastNode());
782 }
783
791 return castToNode(begin().node_);
792 }
793
805 size_t size() const noexcept {
806 if constexpr (TrackSize) return size_;
807
808 size_t cnt = 0;
809 BaseNode* cur = &head_;
810 while (cur->next != nullptr) {
811 ++cnt;
812 cur = cur->next;
813 }
814 return cnt;
815 }
816
825 // Don't use `size()` here, since
826 // it may has linear time complexity
827 return head_.next == nullptr;
828 }
829
830 allocator_type getAllocator() const { return alloc_; }
831
832 iterator begin() noexcept { return iterator(head_.next); }
833 iterator end() noexcept { return iterator(nullptr); }
834
837
840
841 iterator beforeBegin() noexcept { return iterator(&head_); }
844
845 iterator beforeEnd() noexcept { return iterator(getLastNode()); }
848 // reverese_iterator rbegin() ....
849
850 bool operator==(const LinkedList& other) const noexcept {
851 if constexpr (TrackSize) {
852 return size_ == other.size_
853 && equal(begin(), end(), other.begin());
854 }
855 return equal(begin(), end(), other.begin(), other.end());
856 }
857
858 auto operator<=>(const LinkedList& other) const noexcept {
860 other.begin(), other.end());
861 }
862
863private:
883 BaseNode* transferAfter(BaseNode* pos,
884 BaseNode* begin, BaseNode* end) noexcept {
885 // By construction, linked list must know the element
886 // before the first one to transfer, since we have to update
887 // next pointer for it. Also we should know the last element
888 // to transfer for proper pointers rearrangment.
889
890 auto keep = begin->next;
891
892 begin->next = end->next;
893 end->next = pos->next;
894
895 pos->next = keep;
896 return end;
897 }
898
899 Node* castToNode(BaseNode* ptr) const noexcept { return static_cast<Node*>(ptr); }
900
901 void initOptionalFields() noexcept {
902 if constexpr (TrackSize) size_ = 0;
903 if constexpr (TrackLast) last_ = &head_;
904 }
905
914 template <typename... Args>
915 Node* createNode(Args&&... args) {
916 auto node = AllocTraits::template allocate<Node>(alloc_, 1);
917 try {
919 } catch (...) {
920 AllocTraits::deallocate(alloc_, node, 1);
921 throw;
922 }
923 return node;
924 }
925
933 void destroyNode(Node* node) noexcept {
934 AllocTraits::destroy(alloc_, node);
935 AllocTraits::deallocate(alloc_, node, 1);
936 }
937
951 bool isIterToLast(const_iterator it) const noexcept {
952 return it == const_iterator(getLastNode());
953 }
954
974 BaseNode* getLastNode() const noexcept {
975 if constexpr (TrackLast) return last_;
976
977 BaseNode* cur = &head_;
978 while (cur->next != nullptr) cur = cur->next;
979 return cur;
980 }
981
982 // Make mutable for simplicity of const conversions.
983 // Actually needed for const_iterator(&head) for const lists.
984 // But const_iterator can be constructed only from non-const
985 // BaseNodes.
986 mutable BaseNode head_;
987
988 // Enables fast access to the list's size.
989 // If don't track size, use empty struct that
990 // highly(and should in this case) will be optimized and will not use memory.
993
994 // Enables fast access to the last element in the list.
995 // Invariant: if list is empty, points to the `head_`, otherwise to the real Node.
998
999 [[no_unique_address]] Allocator alloc_;
1000};
1001
1003template <typename T, bool TrackLast,
1004 typename Allocator = PrimitiveAllocator>
1006
1008template <typename T, bool TrackSize,
1009 typename Allocator = PrimitiveAllocator>
1011
1013template <typename T,
1014 typename Allocator = PrimitiveAllocator>
1016
1018template <typename T,
1019 typename Allocator = PrimitiveAllocator>
1021
1023template <typename T,
1024 bool TrackSize = true,
1025 bool TrackLast = true, typename Allocator = PrimitiveAllocator>
1030
1031template <InputIterator Iter, typename Allocator = PrimitiveAllocator>
1033 true, true, PrimitiveAllocator>;
1034
1035} // namespace bmb
Unifined interface for allocators.
Definition allocator.h:69
static void deallocate(Alloc &alloc, T *ptr, size_t n)
Deallocates memory under ptr through allocator.
Definition allocator.h:140
static void destroy(Alloc &alloc, T *ptr)
Destroys object throgh alloctor if destroy method is present. Otherwise calls ~T()
Definition allocator.h:107
static void construct(Alloc &alloc, T *ptr, Args &&... args)
Forwards given arguments to allocator's construct method if present. Otherwise uses placement new.
Definition allocator.h:85
Singly linked list.
Definition linked_list.h:53
void swap(LinkedList &other)
Effectively swaps content of the lists.
Definition linked_list.h:198
void reverse() noexcept
Reverses linked list.
Definition linked_list.h:725
LinkedList(std::initializer_list< value_type > init_list, const Allocator &alloc=Allocator())
Initialize list with the given initializer_list.
Definition linked_list.h:144
iterator eraseAfter(const_iterator first, const_iterator last) noexcept
Destroys elements in (first, last).
Definition linked_list.h:440
reference emplaceFront(Args &&... args)
Creates new node from given arguments in the start of the list.
Definition linked_list.h:355
const_iterator cbegin() const noexcept
Definition linked_list.h:838
const value_type & const_reference
Definition linked_list.h:70
LinkedList(Iter first, Iter last, const Allocator &alloc=Allocator())
Initialize list with the given range.
Definition linked_list.h:124
const_iterator beforeBegin() const noexcept
Definition linked_list.h:842
reference back() noexcept
Returns last element in the list.
Definition linked_list.h:767
const_iterator end() const noexcept
Definition linked_list.h:836
~LinkedList()
Definition linked_list.h:187
reference emplaceBack(Args &&... args)
Creates new node from given arguments in the end of the list.
Definition linked_list.h:379
const_iterator cend() const noexcept
Definition linked_list.h:839
iterator beforeBegin() noexcept
Definition linked_list.h:841
iterator beforeEnd() noexcept
Definition linked_list.h:845
T value_type
Definition linked_list.h:68
const_reference back() const noexcept
Returns last element in the list.
Definition linked_list.h:780
iterator spliceAfter(const_iterator pos, LinkedList &&other)
Transfers all elements from other to *this. other becomes empty.
Definition linked_list.h:612
void popBack() noexcept
Destroys last element in the list.
Definition linked_list.h:474
reference front() noexcept
Returns first element in the list.
Definition linked_list.h:749
Node * getRawFirstNode() const noexcept
Returns raw representation of the first list's node.
Definition linked_list.h:790
iterator spliceAfter(const_iterator pos, LinkedList &other, const_iterator first, const_iterator last)
See spliceAfter(const_iterator, LinkedList&&, const_iterator, const_iterator)
Definition linked_list.h:646
LinkedList(size_t count, const value_type &value=value_type(), const Allocator &alloc=Allocator())
Constructs count elements with given value.
Definition linked_list.h:100
auto operator<=>(const LinkedList &other) const noexcept
Definition linked_list.h:858
detail::LListIter< T, true > const_iterator
Definition linked_list.h:76
iterator spliceAfter(const_iterator pos, LinkedList &other)
See spliceAfter(const_iterator, LinkedList&&)
Definition linked_list.h:583
const_reference front() const noexcept
Returns first element in the list.
Definition linked_list.h:756
Allocator allocator_type
Definition linked_list.h:74
iterator spliceAfter(const_iterator pos, LinkedList &&other, const_iterator first, const_iterator last)
Transfers all elements in (first, last) from other to *this right after pos.
Definition linked_list.h:680
iterator end() noexcept
Definition linked_list.h:833
const_iterator beforeEnd() const noexcept
Definition linked_list.h:846
iterator insertAfter(const_iterator pos, value_type value)
Creates element and places it after the given iterator.
Definition linked_list.h:241
value_type * pointer
Definition linked_list.h:71
LinkedList & operator=(LinkedList other)
Copy/move assignment operator.
Definition linked_list.h:182
value_type & reference
Definition linked_list.h:69
size_t size_type
Definition linked_list.h:73
const_iterator cbeforeBegin() const noexcept
Definition linked_list.h:843
LinkedList(const Allocator &alloc)
Definition linked_list.h:82
void popFront() noexcept
Destroys first element in the list.
Definition linked_list.h:457
const_iterator begin() const noexcept
Definition linked_list.h:835
size_t size() const noexcept
Returns list's size.
Definition linked_list.h:805
iterator insertAfter(const_iterator pos, Iter first, Iter last)
Inserts elements from [first, last) in the list after pos.
Definition linked_list.h:287
detail::LListIter< T, false > iterator
Definition linked_list.h:75
allocator_type getAllocator() const
Definition linked_list.h:830
iterator insertAfter(const_iterator pos, std::initializer_list< T > init_list)
Inserts elements from initializer list in the list.
Definition linked_list.h:260
LinkedList(LinkedList &&other) noexcept(noexcept(Allocator(move(other.alloc_))))
Move constructor. No iterators are invalidated.
Definition linked_list.h:161
void merge(LinkedList &&other, Cmp cmp=Cmp())
Merges elements from 2 sorted lists into one sorted.
Definition linked_list.h:520
LinkedList(const LinkedList &other)
Copy constructor.
Definition linked_list.h:153
void clear() noexcept
Removes all elements in the list.
Definition linked_list.h:222
const_iterator cbeforeEnd() const noexcept
Definition linked_list.h:847
iterator eraseAfter(const_iterator pos) noexcept
Destroys element right after pos.
Definition linked_list.h:399
void merge(LinkedList &other, Cmp cmp=Cmp())
See merge(LinkedList&&, auto)
Definition linked_list.h:487
iterator begin() noexcept
Definition linked_list.h:832
iterator emplaceAfter(const_iterator pos, Args &&... args)
Creates new node from given arguments and places it after the given position.
Definition linked_list.h:320
bool isEmpty() const noexcept
Checks whether list has elements.
Definition linked_list.h:824
const value_type * const_pointer
Definition linked_list.h:72
LinkedList() noexcept(noexcept(Allocator()))
Definition linked_list.h:78
bool operator==(const LinkedList &other) const noexcept
Definition linked_list.h:850
Allocator-proxy for new/delete operators.
Definition allocator.h:22
Forward iterator for linked list.
Definition llist_iterator.h:27
Definition algo_base.h:14
constexpr remove_ref_t< T > && move(T &&value) noexcept
Convert a value to xvalue.
Definition move.h:19
Iter next(Iter it, typename IteratorTraits< Iter >::difference_type n=1)
Returns the n-th successor of given iterator.
Definition iterators.h:204
bool equal(Iter1 first1, Iter1 last1, Iter2 first2, Pred pred=Pred())
Checks whether given ranges are equal.
Definition algo_base.h:77
void swap(LinkedList< T, TrackSize, TrackLast, Allocator > &a, LinkedList< T, TrackSize, TrackLast, Allocator > &b)
See LinkedList::swap
Definition linked_list.h:1026
Iter prev(Iter it, typename IteratorTraits< Iter >::difference_type n=1)
Returns the n-th predecessor of given iterator. If n < 0, will chose n-th successor.
Definition iterators.h:221
constexpr T && forward(remove_ref_t< T > &value) noexcept
Forward a lvalue.
Definition move.h:33
auto lexicographical_compare_three_way(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2, Cmp cmp=Cmp())
Compares ranges lexicographically using C++20 three-way comparation.
Definition algo_base.h:37
Definition llist_nodes.h:13
BaseNode * next
Definition llist_nodes.h:14
Definition llist_nodes.h:18