1 // Copyright 2024 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 #pragma once 15 16 #include <cstddef> 17 #include <limits> 18 #include <type_traits> 19 20 #include "pw_containers/internal/intrusive_item.h" 21 #include "pw_containers/internal/intrusive_list_item.h" 22 23 namespace pw::containers::internal { 24 25 /// Generic intrusive list implementation. 26 /// 27 /// This implementation relies on the `Item` type to provide details of how to 28 /// navigate the list. It provides methods similar to `std::forward_list` and 29 /// `std::list`. 30 /// 31 /// @tparam Item The type used to intrusive store list pointers. 32 template <typename Item> 33 class GenericIntrusiveList { 34 public: 35 static_assert(std::is_same_v<Item, IntrusiveForwardListItem> || 36 std::is_same_v<Item, IntrusiveListItem>); 37 GenericIntrusiveList()38 constexpr GenericIntrusiveList() : head_() {} 39 40 template <typename Iterator> GenericIntrusiveList(Iterator first,Iterator last)41 GenericIntrusiveList(Iterator first, Iterator last) : GenericIntrusiveList() { 42 insert_after(&head_, first, last); 43 } 44 45 // Intrusive lists cannot be copied, since each Item can only be in one list. 46 GenericIntrusiveList(const GenericIntrusiveList&) = delete; 47 GenericIntrusiveList& operator=(const GenericIntrusiveList&) = delete; 48 ~GenericIntrusiveList()49 ~GenericIntrusiveList() { CheckIntrusiveContainerIsEmpty(empty()); } 50 51 template <typename Iterator> assign(Iterator first,Iterator last)52 void assign(Iterator first, Iterator last) { 53 clear(); 54 insert_after(&head_, first, last); 55 } 56 57 // Iterators 58 59 /// Returns a pointer to the sentinel item. before_begin()60 constexpr Item* before_begin() noexcept { return &head_; } before_begin()61 constexpr const Item* before_begin() const noexcept { return &head_; } 62 63 /// Returns a pointer to the first item. begin()64 constexpr Item* begin() noexcept { return head_.next_; } begin()65 constexpr const Item* begin() const noexcept { return head_.next_; } 66 67 /// Returns a pointer to the last item. before_end()68 Item* before_end() noexcept { return before_begin()->previous(); } 69 70 /// Returns a pointer to the sentinel item. end()71 constexpr Item* end() noexcept { return &head_; } end()72 constexpr const Item* end() const noexcept { return &head_; } 73 74 // Capacity 75 empty()76 bool empty() const noexcept { return begin() == end(); } 77 78 /// Returns how many items can be added. 79 /// 80 /// As an intrusive container, this is effectively unbounded. max_size()81 constexpr size_t max_size() const noexcept { 82 return std::numeric_limits<ptrdiff_t>::max(); 83 } 84 85 // Modifiers 86 87 /// Removes all items from the list. clear()88 void clear() { 89 while (!empty()) { 90 erase_after(before_begin()); 91 } 92 } 93 94 /// Inserts an item into a list. 95 /// 96 /// The item given by `prev` is updated to point to the item as being next in 97 /// the list, while the item itself points to what `prev` previously pointed 98 /// to as next. 99 /// 100 /// This is O(1). The ownership of the item is not changed. 101 /// 102 /// @return The item that was added. insert_after(Item * prev,Item & item)103 static Item* insert_after(Item* prev, Item& item) { 104 CheckIntrusiveItemIsUncontained(item.unlisted()); 105 item.next_ = prev->next_; 106 item.set_previous(prev); 107 prev->next_ = &item; 108 item.next_->set_previous(&item); 109 return &item; 110 } 111 112 /// Adds items to the list from the provided range after the given item. 113 /// 114 /// This is O(n), where "n" is the number of items in the range. 115 /// 116 /// @return The last item that was added. 117 template <typename Iterator> insert_after(Item * prev,Iterator first,Iterator last)118 static Item* insert_after(Item* prev, Iterator first, Iterator last) { 119 for (Iterator it = first; it != last; ++it) { 120 prev = insert_after(prev, *(FromIterator(it))); 121 } 122 return prev; 123 } 124 125 /// Removes an item from a list. 126 /// 127 /// The item after the given `item` is unlisted, and the item following it is 128 /// returned. 129 /// 130 /// This is O(1). The item is not destroyed.. erase_after(Item * item)131 static Item* erase_after(Item* item) { 132 item->next_->unlist(item); 133 return item->next_; 134 } 135 136 /// Removes the range of items exclusively between `first` and `last`. erase_after(Item * first,Item * last)137 static Item* erase_after(Item* first, Item* last) { 138 while (first->next_ != last) { 139 erase_after(first); 140 } 141 return last; 142 } 143 144 /// Exchanges this list's items with the `other` list's items. swap(GenericIntrusiveList<Item> & other)145 void swap(GenericIntrusiveList<Item>& other) { 146 Item tmp; 147 tmp.replace(head_); 148 head_.replace(other.head_); 149 other.head_.replace(tmp); 150 } 151 152 // Operations 153 154 /// Merges the given `other` list into this one. 155 /// 156 /// After the call, the list will be sorted according to `comp`. The sort is 157 /// stable, and equivalent items in each list will remain in the same order 158 /// relative to each other. 159 template <typename Compare> merge(GenericIntrusiveList<Item> & other,Compare comp)160 void merge(GenericIntrusiveList<Item>& other, Compare comp) { 161 Item* prev = before_begin(); 162 Item* item = begin(); 163 Item* other_item = other.begin(); 164 while (!other.empty()) { 165 if (item == end() || !comp(*item, *other_item)) { 166 Item* tmp = other_item; 167 other_item = other.erase_after(other.before_begin()); 168 prev = insert_after(prev, *tmp); 169 } else { 170 prev = item; 171 item = item->next_; 172 } 173 } 174 } 175 176 /// Moves the items exclusively between `first` and `last` from `other` to 177 /// after `pos` in this list. splice_after(Item * pos,GenericIntrusiveList<Item> & other,Item * first,Item * last)178 static void splice_after(Item* pos, 179 GenericIntrusiveList<Item>& other, 180 Item* first, 181 Item* last) { 182 // Return if the range is empty, unless it is from before_begin to end. 183 if (first == last && first != &other.head_) { 184 return; 185 } 186 Item* first_next = first->next_; 187 if (first_next == last) { 188 return; 189 } 190 Item* last_prev = last->previous(); 191 Item* pos_next = pos->next_; 192 193 first->next_ = last; 194 last->set_previous(first); 195 196 pos->next_ = first_next; 197 first_next->set_previous(pos); 198 199 pos_next->set_previous(last_prev); 200 last_prev->next_ = pos_next; 201 } 202 203 // Removes this specific item from the list, if it is present. Finds the item 204 // by identity (address comparison) rather than value equality. Returns true 205 // if the item was removed; false if it was not present. remove(const Item & item_to_remove)206 bool remove(const Item& item_to_remove) { 207 return remove_if( 208 [&item_to_remove](const Item& item) -> bool { 209 return &item_to_remove == &item; 210 }, 211 1) != 0; 212 } 213 214 /// Removes any item for which the given unary predicate function `p` 215 /// evaluates to true when passed that item. 216 /// 217 /// @tparam UnaryPredicate Function with the signature `bool(const Item&)` 218 /// 219 /// @returns The number of items removed. 220 template <typename UnaryPredicate> 221 size_t remove_if(UnaryPredicate pred, 222 size_t max = std::numeric_limits<size_t>::max()) { 223 size_t removed = 0; 224 Item* prev = before_begin(); 225 while (true) { 226 Item* item = prev->next_; 227 if (item == end()) { 228 break; 229 } 230 if (pred(*item)) { 231 erase_after(prev); 232 ++removed; 233 if (removed == max) { 234 break; 235 } 236 } else { 237 prev = item; 238 } 239 } 240 return removed; 241 } 242 243 /// Reverses the order of items in the list. reverse()244 void reverse() { 245 GenericIntrusiveList<Item> list; 246 while (!empty()) { 247 Item* item = begin(); 248 erase_after(before_begin()); 249 list.insert_after(list.before_begin(), *item); 250 } 251 splice_after(before_begin(), list, list.before_begin(), list.end()); 252 } 253 254 /// Removes consecutive ietms that are equivalent accroding to the given 255 /// binary predicate `p`, leaving only the first item in the list. 256 /// 257 /// @tparam BinaryPredicate Function with the signature 258 /// `bool(const Item&, const Item&)` 259 template <typename BinaryPredicate> unique(BinaryPredicate pred)260 size_t unique(BinaryPredicate pred) { 261 if (empty()) { 262 return 0; 263 } 264 size_t removed = 0; 265 Item* prev = begin(); 266 while (true) { 267 Item* item = prev->next_; 268 if (item == end()) { 269 break; 270 } 271 if (pred(*prev, *item)) { 272 erase_after(prev); 273 ++removed; 274 } else { 275 prev = item; 276 } 277 } 278 return removed; 279 } 280 281 /// Rearranges the items in the list such that the given comparison function 282 /// `comp` evaluates to true for each pair of successive items. 283 /// 284 /// @tparam BinaryPredicate Function with the signature 285 /// `bool(const Item&, const Item&)` 286 template <typename Compare> sort(Compare comp)287 void sort(Compare comp) { 288 Item* mid = begin(); 289 bool even = true; 290 for (Item* item = begin(); item != end(); item = item->next_) { 291 even = !even; 292 if (even) { 293 mid = mid->next_; 294 } 295 } 296 297 // Partition the list. 298 GenericIntrusiveList<Item> tmp; 299 tmp.splice_after(tmp.before_begin(), *this, before_begin(), mid); 300 301 // Check if trivially sorted. 302 if (tmp.empty()) { 303 return; 304 } 305 306 // Sort the sublists. 307 tmp.sort(comp); 308 sort(comp); 309 310 // Merge the sublists. 311 merge(tmp, comp); 312 } 313 314 private: 315 template <typename Iterator> FromIterator(Iterator & it)316 static Item* FromIterator(Iterator& it) { 317 if constexpr (std::is_pointer<std::remove_reference_t<decltype(*it)>>()) { 318 return *it; 319 } else { 320 return &(*it); 321 } 322 } 323 324 // Use an Item for the head pointer. This gives simpler logic for inserting 325 // elements compared to using an Item*. It also makes it possible to use 326 // &head_ for end(), rather than nullptr. This makes end() unique for each 327 // List and ensures that items already in a list cannot be added to another. 328 Item head_; 329 }; 330 331 } // namespace pw::containers::internal 332