C/C++ API Reference
Loading...
Searching...
No Matches
dynamic_map.h
1// Copyright 2025 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 <initializer_list>
17#include <iterator>
18#include <optional>
19#include <tuple>
20#include <type_traits>
21#include <utility>
22
23#include "pw_allocator/allocator.h"
24#include "pw_allocator/unique_ptr.h"
25#include "pw_assert/assert.h"
26#include "pw_containers/internal/traits.h"
27#include "pw_containers/intrusive_map.h"
28
29namespace pw {
30
32
46template <typename Key, typename Value>
48 public:
55 class Node : public IntrusiveMap<Key, Node>::Item {
56 public:
57 using key_type = Key;
58 using mapped_type = Value;
59
60 Node(const Node&) = delete;
61 Node& operator=(const Node&) = delete;
62
63 template <typename K, typename... Args>
64 explicit Node(K&& key, Args&&... args)
65 : key_value_(std::piecewise_construct,
66 std::forward_as_tuple(std::forward<K>(key)),
67 std::forward_as_tuple(std::forward<Args>(args)...)) {}
68
69 const key_type& key() const { return key_value_.first; }
70
71 mapped_type& mapped() { return key_value_.second; }
72 const mapped_type& mapped() const { return key_value_.second; }
73
74 ~Node() = default;
75
76 private:
77 std::pair<const key_type, mapped_type> key_value_;
78 friend class DynamicMap;
79 };
80
81 private:
83
87 template <bool kIsConst>
88 class Iterator {
89 public:
90 using iterator_category = std::bidirectional_iterator_tag;
91 using value_type = std::pair<const Key, Value>;
92 using difference_type = std::ptrdiff_t;
93 using reference =
94 std::conditional_t<kIsConst, const value_type&, value_type&>;
95 using pointer =
96 std::conditional_t<kIsConst, const value_type*, value_type*>;
97
98 constexpr Iterator() = default;
99
100 constexpr reference operator*() const { return it_->key_value_; }
101 constexpr pointer operator->() const { return &it_->key_value_; }
102
103 constexpr Iterator operator++() {
104 ++it_;
105 return *this;
106 }
107 constexpr Iterator operator++(int) {
108 Iterator result = *this;
109 ++it_;
110 return result;
111 }
112 constexpr Iterator operator--() {
113 --it_;
114 return *this;
115 }
116 constexpr Iterator operator--(int) {
117 Iterator result = *this;
118 --it_;
119 return result;
120 }
121
122 constexpr friend bool operator==(Iterator lhs, Iterator rhs) {
123 return lhs.it_ == rhs.it_;
124 }
125 constexpr friend bool operator!=(Iterator lhs, Iterator rhs) {
126 return lhs.it_ != rhs.it_;
127 }
128
129 private:
130 using BaseIterator = std::conditional_t<kIsConst,
131 typename Map::const_iterator,
132 typename Map::iterator>;
133
134 constexpr explicit Iterator(BaseIterator it) : it_(it) {}
135
136 template <bool>
137 friend class Iterator;
138 friend class DynamicMap;
139
140 BaseIterator it_;
141 };
142
143 public:
144 using key_type = Key;
145 using mapped_type = Value;
146 using value_type = std::pair<const Key, Value>;
147 using size_type = typename Map::size_type;
148 using difference_type = typename Map::difference_type;
149 using key_compare = typename Map::key_compare;
150 using allocator_type = Allocator;
151 using reference = value_type&;
152 using const_reference = const value_type&;
153 using pointer = value_type*;
154 using const_pointer = const value_type*;
155 using iterator = Iterator<false>;
156 using const_iterator = Iterator<true>;
157 using reverse_iterator = std::reverse_iterator<iterator>;
158 using const_reverse_iterator = std::reverse_iterator<const_iterator>;
159 using node_type = Node;
160
163 iterator position;
164 bool inserted;
166 };
167
172 constexpr explicit DynamicMap(Allocator& allocator) noexcept
173 : allocator_(&allocator), map_() {}
174
177 DynamicMap(const DynamicMap&) = delete;
178 DynamicMap& operator=(const DynamicMap&) = delete;
179
182 DynamicMap(DynamicMap&& other) noexcept {
183 allocator_ = other.allocator_;
184 map_.swap(other.map_);
185 }
186
189 DynamicMap& operator=(DynamicMap&& other) noexcept {
190 if (&other == this) {
191 return *this;
192 }
193 clear();
194 allocator_ = other.allocator_;
195 map_.swap(other.map_);
196 return *this;
197 }
198
199 ~DynamicMap() { clear(); }
200
202 constexpr allocator_type& get_allocator() const { return *allocator_; }
203
204 // Iterators
205
206 iterator begin() noexcept { return iterator(map_.begin()); }
207 const_iterator begin() const noexcept { return const_iterator(map_.begin()); }
208 const_iterator cbegin() const noexcept { return begin(); }
209 iterator end() noexcept { return iterator(map_.end()); }
210 const_iterator end() const noexcept { return const_iterator(map_.end()); }
211 const_iterator cend() const noexcept { return end(); }
212 reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
213 const_reverse_iterator rbegin() const noexcept {
214 return const_reverse_iterator(end());
215 }
216 const_reverse_iterator crbegin() const noexcept { return rbegin(); }
217 reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
218 const_reverse_iterator rend() const noexcept {
219 return const_reverse_iterator(begin());
220 }
221 const_reverse_iterator crend() const noexcept { return rend(); }
222
223 // Element access
224
227 mapped_type& at(const key_type& key) { return map_.at(key).mapped(); }
228
229 const mapped_type& at(const key_type& key) const {
230 return map_.at(key).mapped();
231 }
232
237 template <typename U = mapped_type,
238 typename = std::enable_if_t<std::is_default_constructible_v<U>>>
239 mapped_type& operator[](const key_type& key) {
240 return emplace(key).first->second;
241 }
242
243 // Capacity
244
245 [[nodiscard]] bool empty() const { return map_.empty(); }
246 size_type size() const { return map_.size(); }
247 constexpr size_type max_size() const noexcept { return map_.max_size(); }
248
250 void clear() {
251 while (!empty()) {
252 erase(begin());
253 }
254 }
255
256 // Modifiers
257
261 [[nodiscard]] std::optional<std::pair<iterator, bool>> try_insert(
262 const value_type& value) {
263 return try_emplace(value.first, value.second);
264 }
265
266 // Moving into a fallible insertion is deleted to prevent "ghost moves."
267 // If allocation fails, the object would be moved-from but not stored.
268 // Use try_emplace instead to ensure moves only occur on success.
269 std::optional<std::pair<iterator, bool>> try_insert(value_type&&) = delete;
270
272 std::pair<iterator, bool> insert(const value_type& value) {
273 return emplace(value.first, value.second);
274 }
275
276 std::pair<iterator, bool> insert(value_type&& value) {
277 return emplace(std::move(value.first), std::move(value.second));
278 }
279
281 template <typename InputIt,
282 typename = containers::internal::EnableIfInputIterator<InputIt>>
283 void insert(InputIt first, InputIt last) {
284 for (auto it = first; it != last; ++it) {
285 emplace(it->first, it->second);
286 }
287 }
288
289 void insert(std::initializer_list<value_type> ilist) {
290 insert(ilist.begin(), ilist.end());
291 }
292
297 if (node == nullptr) {
298 return {end(), false, nullptr};
299 }
300 PW_ASSERT(node.deallocator() == allocator_);
301 auto [node_it, inserted] = map_.insert(*node);
302 if (inserted) {
303 node.Release();
304 return {iterator(node_it), true, nullptr};
305 }
306 return {iterator(node_it), false, std::move(node)};
307 }
308
309 // TODO: b/483691699 - Support hint-optimized insertion
310
314 template <typename K, typename... Args>
315 [[nodiscard]] std::optional<std::pair<iterator, bool>> try_emplace(
316 K&& key, Args&&... args) {
317 return TryEmplaceImpl(std::forward<K>(key), std::forward<Args>(args)...);
318 }
319
321 template <typename K, typename... Args>
322 std::pair<iterator, bool> emplace(K&& key, Args&&... args) {
323 auto result =
324 try_emplace(std::forward<K>(key), std::forward<Args>(args)...);
325 PW_ASSERT(result.has_value());
326 return result.value();
327 }
328
330 iterator erase(iterator pos) {
331 node_type* node = &(*pos.it_);
332 auto next_it = map_.erase(pos.it_);
333 allocator_->Delete(node);
334 return iterator(next_it);
335 }
336
337 iterator erase(const_iterator pos) {
338 return erase(iterator(typename Map::iterator(pos.it_)));
339 }
340
342 iterator erase(iterator first, iterator last) {
343 while (first != last) {
344 first = erase(first);
345 }
346 return last;
347 }
348
351 template <typename K>
352 size_type erase(K&& key) {
353 auto it = find(key);
354 if (it == end()) {
355 return 0;
356 }
357 erase(it);
358 return 1;
359 }
360
363 node_type* node = &(*pos.it_);
364 map_.erase(pos.it_);
365 return UniquePtr<node_type>(node, *allocator_);
366 }
367
368 UniquePtr<node_type> take(const_iterator pos) {
369 return take(iterator(typename Map::iterator(pos.it_)));
370 }
371
373 template <typename K>
375 auto it = find(key);
376 if (it == end()) {
377 return nullptr;
378 }
379 return take(it);
380 }
381
383 void swap(DynamicMap& other) {
384 std::swap(allocator_, other.allocator_);
385 map_.swap(other.map_);
386 }
387
391 void merge(DynamicMap& other) {
392 if (this == &other) {
393 return;
394 }
395 PW_ASSERT(allocator_ == other.allocator_);
396 if (empty()) {
397 swap(other);
398 return;
399 }
400 for (auto it = other.begin(); it != other.end();) {
401 if (contains(it->first)) {
402 ++it;
403 } else {
404 insert(other.take(it++));
405 }
406 }
407 }
408
409 void merge(DynamicMap&& other) { merge(other); }
410
411 // Lookup
412
413 size_type count(const key_type& key) { return contains(key) ? 1 : 0; }
414
415 iterator find(const key_type& key) { return iterator(map_.find(key)); }
416
417 const_iterator find(const key_type& key) const {
418 return const_iterator(map_.find(key));
419 }
420
421 [[nodiscard]] bool contains(const key_type& key) const {
422 return find(key) != end();
423 }
424
425 std::pair<iterator, iterator> equal_range(const key_type& key) {
426 auto result = map_.equal_range(key);
427 return std::make_pair(iterator(result.first), iterator(result.second));
428 }
429
430 std::pair<const_iterator, const_iterator> equal_range(
431 const key_type& key) const {
432 auto result = map_.equal_range(key);
433 return std::make_pair(const_iterator(result.first),
434 const_iterator(result.second));
435 }
436
437 iterator lower_bound(const key_type& key) {
438 return iterator(map_.lower_bound(key));
439 }
440
441 const_iterator lower_bound(const key_type& key) const {
442 return const_iterator(map_.lower_bound(key));
443 }
444
445 iterator upper_bound(const key_type& key) {
446 return iterator(map_.upper_bound(key));
447 }
448
449 const_iterator upper_bound(const key_type& key) const {
450 return const_iterator(map_.upper_bound(key));
451 }
452
453 private:
456 template <typename K, typename... Args>
457 [[nodiscard]] std::optional<std::pair<iterator, bool>> TryEmplaceImpl(
458 K&& key, Args&&... args) {
459 // Perform lookup before allocation to avoid unnecessary heap usage.
460 if (auto it = map_.find(key); it != map_.end()) {
461 return std::make_pair(iterator(it), false);
462 }
463 node_type* node = allocator_->template New<node_type>(
464 std::forward<K>(key), std::forward<Args>(args)...);
465 if (node == nullptr) {
466 return std::nullopt;
467 }
468 auto [node_it, inserted] = map_.insert(*node);
469 PW_ASSERT(inserted);
470 return std::make_pair(iterator(node_it), true);
471 }
472
473 Allocator* allocator_;
474 Map map_;
475};
476
477} // namespace pw
Definition: allocator.h:42
void Delete(T *ptr)
Definition: deallocator.h:325
Definition: dynamic_map.h:55
Definition: dynamic_map.h:47
Definition: intrusive_map.h:71
Definition: unique_ptr.h:44
std::optional< std::pair< iterator, bool > > try_insert(const value_type &value)
Definition: dynamic_map.h:261
iterator erase(iterator pos)
Removes the element at pos and deallocates the node.
Definition: dynamic_map.h:330
constexpr allocator_type & get_allocator() const
Returns the allocator used by this map.
Definition: dynamic_map.h:202
void swap(IntrusiveMap< Key, T > &other)
Exchanges this map's items with the other map's items.
Definition: intrusive_map.h:275
mapped_type & operator[](const key_type &key)
Definition: dynamic_map.h:239
constexpr DynamicMap(Allocator &allocator) noexcept
Definition: dynamic_map.h:172
T & at(const key_type &key)
Definition: intrusive_map.h:178
std::pair< iterator, iterator > equal_range(const key_type &key)
Definition: intrusive_map.h:299
iterator lower_bound(const key_type &key)
Definition: intrusive_map.h:312
void merge(DynamicMap &other)
Definition: dynamic_map.h:391
iterator upper_bound(const key_type &key)
Definition: intrusive_map.h:321
mapped_type & at(const key_type &key)
Definition: dynamic_map.h:227
std::pair< iterator, bool > insert(T &item)
Definition: intrusive_map.h:241
DynamicMap & operator=(DynamicMap &&other) noexcept
Definition: dynamic_map.h:189
insert_return_type insert(UniquePtr< node_type > &&node)
Definition: dynamic_map.h:296
iterator erase(T &item)
Definition: intrusive_map.h:264
DynamicMap(DynamicMap &&other) noexcept
Definition: dynamic_map.h:182
size_t size() const
Returns the number of items in the map.
Definition: intrusive_map.h:220
iterator find(const key_type &key)
Definition: intrusive_map.h:290
size_type erase(K &&key)
Definition: dynamic_map.h:352
std::pair< iterator, bool > insert(const value_type &value)
Inserts a value into the map. Crashes on allocation failure.
Definition: dynamic_map.h:272
void swap(DynamicMap &other)
Swaps the contents and allocators of two maps. No allocations occur.
Definition: dynamic_map.h:383
UniquePtr< node_type > take(K &&key)
Removes the element with matching key and returns it as a UniquePtr.
Definition: dynamic_map.h:374
constexpr size_t max_size() const noexcept
Definition: intrusive_map.h:225
std::optional< std::pair< iterator, bool > > try_emplace(K &&key, Args &&... args)
Definition: dynamic_map.h:315
bool empty() const noexcept
Returns whether the map has zero items or not.
Definition: intrusive_map.h:217
std::pair< iterator, bool > emplace(K &&key, Args &&... args)
Constructs an element in-place. Crashes on allocation failure.
Definition: dynamic_map.h:322
void clear()
Removes all elements from the map and deallocates each node.
Definition: dynamic_map.h:250
UniquePtr< node_type > take(iterator pos)
Removes the element at pos and returns it as a UniquePtr.
Definition: dynamic_map.h:362
iterator erase(iterator first, iterator last)
Removes elements in the range [first, last).
Definition: dynamic_map.h:342
void insert(InputIt first, InputIt last)
Inserts a range of elements. Crashes on allocation failure.
Definition: dynamic_map.h:283
DynamicMap(const DynamicMap &)=delete
The Pigweed namespace.
Definition: alignment.h:27
Return type for node-based insertion.
Definition: dynamic_map.h:162