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 "pw_containers/internal/aa_tree.h" 17 #include "pw_containers/internal/aa_tree_item.h" 18 19 namespace pw { 20 21 /// A `std::map<Key, T, Compare>`-like class that uses intrusive items. 22 /// 23 /// Since the map structure is stored in the items themselves, each item must 24 /// outlive any map it is a part of and must be part of at most one map. 25 /// 26 /// This map requires unique keys. Attempting to add an item with same key as an 27 /// item already in the map will fail. 28 /// 29 /// - Since items are not allocated by this class, the following methods have 30 /// no analogue: 31 /// - std::map<T>::operator= 32 /// - std::map<T>::operator[] 33 /// - std::map<T>::get_allocator 34 /// - std::map<T>::insert_or_assign 35 /// - std::map<T>::emplace 36 /// - std::map<T>::emplace_hint 37 /// - std::map<T>::try_emplace 38 /// 39 /// - Methods corresponding to the following take initializer lists of pointer 40 /// to items rather than the items themselves: 41 /// - std::map<T>::(constructor) 42 /// - std::map<T>::insert 43 /// 44 /// - There are no overloads corresponding to the following methods that take 45 /// r-value references.: 46 /// - std::map<T>::insert 47 /// - std::map<T>::merge 48 /// 49 /// - Since modifying the map modifies the items themselves, methods 50 /// corresponding to those below only take `iterator`s and not 51 /// `const_iterator`s: 52 /// - std::map<T>::insert 53 /// - std::map<T>::erase 54 /// 55 /// - An additional overload of `erase` is provided that takes a direct 56 /// reference to an item. 57 /// 58 /// - C++23 methods are not (yet) supported. 59 /// 60 /// @tparam Key Type to sort items on 61 /// @tparam T Type of values stored in the map. 62 template <typename Key, typename T> 63 class IntrusiveMap { 64 private: 65 using GenericIterator = containers::internal::GenericAATree::iterator; 66 using Tree = containers::internal::AATree<Key, T>; 67 using Compare = typename Tree::Compare; 68 using GetKey = typename Tree::GetKey; 69 70 public: 71 /// IntrusiveMap items must derive from either `Item` or `Pair`. 72 /// Use `Pair` to automatically provide storage for a `Key`. 73 /// Use `Item` when the derived type has a `key()` accessor method or when the 74 /// map provides a custom `GetKey` function object. 75 using Item = typename Tree::Item; 76 using Pair = typename Tree::Pair; 77 78 using key_type = Key; 79 using mapped_type = std::remove_cv_t<T>; 80 using value_type = Item; 81 using size_type = std::size_t; 82 using difference_type = std::ptrdiff_t; 83 using key_compare = Compare; 84 using reference = value_type&; 85 using const_reference = const value_type&; 86 using pointer = value_type*; 87 using const_pointer = const value_type*; 88 89 public: 90 class iterator : public containers::internal::AATreeIterator<T> { 91 public: 92 constexpr iterator() = default; 93 94 private: 95 friend IntrusiveMap; iterator(GenericIterator iter)96 constexpr explicit iterator(GenericIterator iter) 97 : containers::internal::AATreeIterator<T>(iter) {} 98 }; 99 100 class const_iterator 101 : public containers::internal::AATreeIterator<std::add_const_t<T>> { 102 public: 103 constexpr const_iterator() = default; 104 105 private: 106 friend IntrusiveMap; const_iterator(GenericIterator iter)107 constexpr explicit const_iterator(GenericIterator iter) 108 : containers::internal::AATreeIterator<std::add_const_t<T>>(iter) {} 109 }; 110 111 using reverse_iterator = std::reverse_iterator<iterator>; 112 using const_reverse_iterator = std::reverse_iterator<const_iterator>; 113 114 /// Constructs an empty map of items. IntrusiveMap()115 constexpr explicit IntrusiveMap() : IntrusiveMap(std::less<Key>()) {} 116 117 /// Constructs an empty map of items. 118 /// 119 /// SFINAE is used to disambiguate between this constructor and the one that 120 /// takes an initializer list. 121 /// 122 /// @param compare Function with the signature `bool(Key, Key)` that is 123 /// used to order items. 124 template <typename Comparator> IntrusiveMap(Comparator && compare)125 constexpr explicit IntrusiveMap(Comparator&& compare) 126 : IntrusiveMap(std::forward<Comparator>(compare), 127 [](const T& t) { return t.key(); }) {} 128 129 /// Constructs an empty map of items. 130 /// 131 /// @param compare Function with the signature `bool(Key, Key)` that is 132 /// used to order items. 133 /// @param get_key Function with signature `Key(const T&)` that returns the 134 /// value that items are sorted on. 135 template <typename Comparator, typename KeyRetriever> IntrusiveMap(Comparator && compare,KeyRetriever && get_key)136 constexpr IntrusiveMap(Comparator&& compare, KeyRetriever&& get_key) 137 : tree_(true, 138 std::forward<Comparator>(compare), 139 std::forward<KeyRetriever>(get_key)) { 140 CheckItemType(); 141 } 142 143 /// Constructs an IntrusiveMap from an iterator over Items. 144 /// 145 /// The iterator may dereference as either Item& (e.g. from std::array<Item>) 146 /// or Item* (e.g. from std::initializer_list<Item*>). 147 template <typename Iterator, typename... Functors> IntrusiveMap(Iterator first,Iterator last,Functors &&...functors)148 IntrusiveMap(Iterator first, Iterator last, Functors&&... functors) 149 : IntrusiveMap(std::forward<Functors>(functors)...) { 150 tree_.insert(first, last); 151 } 152 153 /// Constructs an IntrusiveMap from a std::initializer_list of pointers 154 /// to items. 155 template <typename... Functors> IntrusiveMap(std::initializer_list<T * > items,Functors &&...functors)156 explicit IntrusiveMap(std::initializer_list<T*> items, Functors&&... functors) 157 : IntrusiveMap( 158 items.begin(), items.end(), std::forward<Functors>(functors)...) {} 159 160 // Element access 161 162 /// Returns a reference to the item associated with the given key. 163 /// 164 /// @pre The map must contain an item associated with the key. at(const key_type & key)165 T& at(const key_type& key) { 166 auto iter = tree_.find(key); 167 PW_DASSERT(iter != tree_.end()); 168 return static_cast<T&>(*iter); 169 } 170 at(const key_type & key)171 const T& at(const key_type& key) const { 172 auto iter = tree_.find(key); 173 PW_DASSERT(iter != tree_.end()); 174 return static_cast<const T&>(*iter); 175 } 176 177 // Iterators 178 begin()179 iterator begin() noexcept { return iterator(tree_.begin()); } begin()180 const_iterator begin() const noexcept { 181 return const_iterator(tree_.begin()); 182 } cbegin()183 const_iterator cbegin() const noexcept { return begin(); } 184 end()185 iterator end() noexcept { return iterator(tree_.end()); } end()186 const_iterator end() const noexcept { return const_iterator(tree_.end()); } cend()187 const_iterator cend() const noexcept { return end(); } 188 rbegin()189 reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } rbegin()190 const_reverse_iterator rbegin() const noexcept { 191 return const_reverse_iterator(end()); 192 } crbegin()193 const_reverse_iterator crbegin() const noexcept { return rbegin(); } 194 rend()195 reverse_iterator rend() noexcept { return reverse_iterator(begin()); } rend()196 const_reverse_iterator rend() const noexcept { 197 return const_reverse_iterator(begin()); 198 } crend()199 const_reverse_iterator crend() const noexcept { return rend(); } 200 201 // Capacity 202 203 /// Returns whether the map has zero items or not. empty()204 [[nodiscard]] bool empty() const noexcept { return tree_.empty(); } 205 206 /// Returns the number of items in the map. size()207 size_t size() const { return tree_.size(); } 208 209 /// Returns how many items can be added. 210 /// 211 /// As an intrusive container, this is effectively unbounded. max_size()212 constexpr size_t max_size() const noexcept { return tree_.max_size(); } 213 214 // Modifiers 215 216 /// Removes all items from the map and leaves it empty. 217 /// 218 /// The items themselves are not destructed. clear()219 void clear() { tree_.clear(); } 220 221 /// Attempts to add the given item to the map. 222 /// 223 /// The item will be added if the map does not already contain an item with 224 /// the given item's key. 225 /// 226 /// @returns A pointer to the inserted item and `true`, or a pointer to the 227 /// existing item with same key and `false`. insert(T & item)228 std::pair<iterator, bool> insert(T& item) { 229 auto result = tree_.insert(item); 230 return std::make_pair(iterator(result.first), result.second); 231 } 232 insert(iterator,T & item)233 iterator insert(iterator, T& item) { 234 // Disregard the hint. 235 return insert(item).first; 236 } 237 238 template <class Iterator> insert(Iterator first,Iterator last)239 void insert(Iterator first, Iterator last) { 240 tree_.insert(first, last); 241 } 242 insert(std::initializer_list<T * > ilist)243 void insert(std::initializer_list<T*> ilist) { 244 tree_.insert(ilist.begin(), ilist.end()); 245 } 246 247 /// Removes an item from the map and returns an iterator to the item after 248 /// the removed item.. 249 /// 250 /// The items themselves are not destructed. erase(T & item)251 iterator erase(T& item) { return iterator(tree_.erase_one(item)); } 252 erase(iterator pos)253 iterator erase(iterator pos) { return iterator(tree_.erase_one(*pos)); } 254 erase(iterator first,iterator last)255 iterator erase(iterator first, iterator last) { 256 return iterator(tree_.erase_range(*first, *last)); 257 } 258 erase(const key_type & key)259 size_t erase(const key_type& key) { return tree_.erase_all(key); } 260 261 /// Exchanges this map's items with the `other` map's items. swap(IntrusiveMap<Key,T> & other)262 void swap(IntrusiveMap<Key, T>& other) { tree_.swap(other.tree_); } 263 264 /// Splices items from the `other` map into this one. 265 template <typename MapType> merge(MapType & other)266 void merge(MapType& other) { 267 tree_.merge(other.tree_); 268 } 269 270 /// Returns the number of items in the map with the given key. 271 /// 272 /// Since the map requires unique keys, this is always 0 or 1. count(const key_type & key)273 size_t count(const key_type& key) const { return tree_.count(key); } 274 275 /// Returns a pointer to an item with the given key, or null if the map does 276 /// not contain such an item. find(const key_type & key)277 iterator find(const key_type& key) { return iterator(tree_.find(key)); } 278 find(const key_type & key)279 const_iterator find(const key_type& key) const { 280 return const_iterator(tree_.find(key)); 281 } 282 283 /// Returns a pair of iterators where the first points to the item with the 284 /// smallest key that is not less than the given key, and the second points to 285 /// the item with the smallest key that is greater than the given key. equal_range(const key_type & key)286 std::pair<iterator, iterator> equal_range(const key_type& key) { 287 auto result = tree_.equal_range(key); 288 return std::make_pair(iterator(result.first), iterator(result.second)); 289 } equal_range(const key_type & key)290 std::pair<const_iterator, const_iterator> equal_range( 291 const key_type& key) const { 292 auto result = tree_.equal_range(key); 293 return std::make_pair(const_iterator(result.first), 294 const_iterator(result.second)); 295 } 296 297 /// Returns an iterator to the item in the map with the smallest key that is 298 /// greater than or equal to the given key, or `end()` if the map is empty. lower_bound(const key_type & key)299 iterator lower_bound(const key_type& key) { 300 return iterator(tree_.lower_bound(key)); 301 } lower_bound(const key_type & key)302 const_iterator lower_bound(const key_type& key) const { 303 return const_iterator(tree_.lower_bound(key)); 304 } 305 306 /// Returns an iterator to the item in the map with the smallest key that is 307 /// strictly greater than the given key, or `end()` if the map is empty. upper_bound(const key_type & key)308 iterator upper_bound(const key_type& key) { 309 return iterator(tree_.upper_bound(key)); 310 } upper_bound(const key_type & key)311 const_iterator upper_bound(const key_type& key) const { 312 return const_iterator(tree_.upper_bound(key)); 313 } 314 315 private: 316 // Check that T is an Item in a function, since the class T will not be fully 317 // defined when the IntrusiveList<T> class is instantiated. CheckItemType()318 static constexpr void CheckItemType() { 319 using ItemBase = containers::internal::AATreeItem; 320 using IntrusiveItemType = 321 typename containers::internal::IntrusiveItem<ItemBase, T>::Type; 322 static_assert( 323 std::is_base_of<IntrusiveItemType, T>(), 324 "IntrusiveMap items must be derived from IntrusiveMap<Key, T>::Item, " 325 "where T is the item or one of its bases."); 326 } 327 328 // Allow multimaps to access the tree for `merge`. 329 template <typename, typename> 330 friend class IntrusiveMultiMap; 331 332 // The AA tree that stores the map. 333 // 334 // This field is mutable so that it doesn't need const overloads. 335 mutable Tree tree_; 336 }; 337 338 } // namespace pw 339