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_assert/assert.h"
25#include "pw_containers/internal/traits.h"
26#include "pw_containers/intrusive_map.h"
27
28namespace pw {
29
31
45template <typename Key, typename Value>
47 private:
51 class MapItem : public IntrusiveMap<Key, MapItem>::Item {
52 public:
53 using IntrusiveMap<Key, MapItem>::Item::Item;
54
57 template <typename K, typename... Args>
58 explicit MapItem(K&& key, Args&&... args)
59 : Map::Item(),
60 key_value(std::piecewise_construct,
61 std::forward_as_tuple(std::forward<K>(key)),
62 std::forward_as_tuple(std::forward<Args>(args)...)) {}
63
64 const Key& key() const { return key_value.first; }
65 Value& value() { return key_value.second; }
66 const Value& value() const { return key_value.second; }
67
68 private:
69 std::pair<const Key, Value> key_value;
70 friend class DynamicMap;
71 };
72 using Map = IntrusiveMap<Key, MapItem>;
73
77 template <bool kIsConst>
78 class Iterator {
79 public:
80 using iterator_category = std::bidirectional_iterator_tag;
81 using value_type = std::pair<const Key, Value>;
82 using difference_type = std::ptrdiff_t;
83 using reference =
84 std::conditional_t<kIsConst, const value_type&, value_type&>;
85 using pointer =
86 std::conditional_t<kIsConst, const value_type*, value_type*>;
87
88 constexpr Iterator() = default;
89
90 constexpr reference operator*() const { return it_->key_value; }
91 constexpr pointer operator->() const { return &it_->key_value; }
92
93 constexpr Iterator operator++() {
94 ++it_;
95 return *this;
96 }
97 constexpr Iterator operator++(int) {
98 Iterator result = *this;
99 ++it_;
100 return result;
101 }
102 constexpr Iterator operator--() {
103 --it_;
104 return *this;
105 }
106 constexpr Iterator operator--(int) {
107 Iterator result = *this;
108 --it_;
109 return result;
110 }
111
112 constexpr friend bool operator==(Iterator lhs, Iterator rhs) {
113 return lhs.it_ == rhs.it_;
114 }
115 constexpr friend bool operator!=(Iterator lhs, Iterator rhs) {
116 return lhs.it_ != rhs.it_;
117 }
118
119 private:
120 using BaseIterator = std::conditional_t<kIsConst,
121 typename Map::const_iterator,
122 typename Map::iterator>;
123
124 constexpr explicit Iterator(BaseIterator it) : it_(it) {}
125
126 template <bool>
127 friend class Iterator;
128 friend class DynamicMap;
129
130 BaseIterator it_;
131 };
132
133 public:
134 using key_type = Key;
135 using mapped_type = Value;
136 using value_type = std::pair<const Key, Value>;
137 using size_type = typename Map::size_type;
138 using difference_type = typename Map::difference_type;
139 using key_compare = typename Map::key_compare;
140 using allocator_type = Allocator;
141 using reference = value_type&;
142 using const_reference = const value_type&;
143 using pointer = value_type*;
144 using const_pointer = const value_type*;
145 using iterator = Iterator<false>;
146 using const_iterator = Iterator<true>;
147 using reverse_iterator = std::reverse_iterator<iterator>;
148 using const_reverse_iterator = std::reverse_iterator<const_iterator>;
149 using insert_return_type = std::pair<iterator, bool>;
150
155 constexpr explicit DynamicMap(Allocator& allocator) noexcept
156 : allocator_(&allocator), map_() {}
157
160 DynamicMap(const DynamicMap&) = delete;
161 DynamicMap& operator=(const DynamicMap&) = delete;
162
165 DynamicMap(DynamicMap&& other) noexcept {
166 allocator_ = other.allocator_;
167 map_.swap(other.map_);
168 }
169
172 DynamicMap& operator=(DynamicMap&& other) noexcept {
173 if (&other == this) {
174 return *this;
175 }
176 clear();
177 allocator_ = other.allocator_;
178 map_.swap(other.map_);
179 return *this;
180 }
181
182 ~DynamicMap() { clear(); }
183
185 constexpr allocator_type& get_allocator() const { return *allocator_; }
186
187 // Iterators
188
189 iterator begin() noexcept { return iterator(map_.begin()); }
190 const_iterator begin() const noexcept { return const_iterator(map_.begin()); }
191 const_iterator cbegin() const noexcept { return begin(); }
192 iterator end() noexcept { return iterator(map_.end()); }
193 const_iterator end() const noexcept { return const_iterator(map_.end()); }
194 const_iterator cend() const noexcept { return end(); }
195 reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
196 const_reverse_iterator rbegin() const noexcept {
197 return const_reverse_iterator(end());
198 }
199 const_reverse_iterator crbegin() const noexcept { return rbegin(); }
200 reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
201 const_reverse_iterator rend() const noexcept {
202 return const_reverse_iterator(begin());
203 }
204 const_reverse_iterator crend() const noexcept { return rend(); }
205
206 // Element access
207
210 mapped_type& at(const key_type& key) { return map_.at(key).value(); }
211
212 const mapped_type& at(const key_type& key) const {
213 return map_.at(key).value();
214 }
215
220 template <typename U = mapped_type,
221 typename = std::enable_if_t<std::is_default_constructible_v<U>>>
222 mapped_type& operator[](const key_type& key) {
223 return emplace(key).first->second;
224 }
225
226 // Capacity
227
228 [[nodiscard]] bool empty() const { return map_.empty(); }
229 size_type size() const { return map_.size(); }
230 constexpr size_type max_size() const noexcept { return map_.max_size(); }
231
233 void clear() {
234 while (!empty()) {
235 erase(begin());
236 }
237 }
238
239 // Modifiers
240
244 [[nodiscard]] std::optional<insert_return_type> try_insert(
245 const value_type& value) {
246 return try_emplace(value.first, value.second);
247 }
248
249 // Moving into a fallible insertion is deleted to prevent "ghost moves."
250 // If allocation fails, the object would be moved-from but not stored.
251 // Use try_emplace instead to ensure moves only occur on success.
252 std::optional<insert_return_type> try_insert(value_type&&) = delete;
253
255 insert_return_type insert(const value_type& value) {
256 return emplace(value.first, value.second);
257 }
258
259 insert_return_type insert(value_type&& value) {
260 return emplace(std::move(value.first), std::move(value.second));
261 }
262
264 template <typename InputIt,
265 typename = containers::internal::EnableIfInputIterator<InputIt>>
266 void insert(InputIt first, InputIt last) {
267 for (auto it = first; it != last; ++it) {
268 emplace(it->first, it->second);
269 }
270 }
271
272 void insert(std::initializer_list<value_type> ilist) {
273 insert(ilist.begin(), ilist.end());
274 }
275
276 // TODO: b/483691699 - Support hint-optimized insertion
277
281 template <typename K, typename... Args>
282 [[nodiscard]] std::optional<std::pair<iterator, bool>> try_emplace(
283 K&& key, Args&&... args) {
284 return TryEmplaceImpl(std::forward<K>(key), std::forward<Args>(args)...);
285 }
286
288 template <typename K, typename... Args>
289 std::pair<iterator, bool> emplace(K&& key, Args&&... args) {
290 auto result =
291 try_emplace(std::forward<K>(key), std::forward<Args>(args)...);
292 PW_ASSERT(result.has_value());
293 return result.value();
294 }
295
297 iterator erase(iterator pos) {
298 MapItem* item = &(*pos.it_);
299 auto next_it = map_.erase(pos.it_);
300 allocator_->Delete(item);
301 return iterator(next_it);
302 }
303
304 iterator erase(const_iterator pos) {
305 return erase(iterator(typename Map::iterator(pos.it_)));
306 }
307
309 iterator erase(iterator first, iterator last) {
310 while (first != last) {
311 first = erase(first);
312 }
313 return last;
314 }
315
318 template <typename K>
319 size_type erase(K&& key) {
320 auto it = find(key);
321 if (it == end()) {
322 return 0;
323 }
324 erase(it);
325 return 1;
326 }
327
329 void swap(DynamicMap& other) {
330 std::swap(allocator_, other.allocator_);
331 map_.swap(other.map_);
332 }
333
336 void merge(DynamicMap& other) {
337 for (auto it = other.begin(); it != other.end();) {
338 auto result = try_emplace(std::move(it->first), std::move(it->second));
339 if (result.has_value() && result.value().second) {
340 it = other.erase(it);
341 } else {
342 ++it;
343 }
344 }
345 }
346
347 void merge(DynamicMap&& other) { merge(other); }
348
349 // Lookup
350
351 size_type count(const key_type& key) { return contains(key) ? 1 : 0; }
352
353 iterator find(const key_type& key) { return iterator(map_.find(key)); }
354
355 const_iterator find(const key_type& key) const {
356 return const_iterator(map_.find(key));
357 }
358
359 [[nodiscard]] bool contains(const key_type& key) const {
360 return find(key) != end();
361 }
362
363 std::pair<iterator, iterator> equal_range(const key_type& key) {
364 auto result = map_.equal_range(key);
365 return std::make_pair(iterator(result.first), iterator(result.second));
366 }
367
368 std::pair<const_iterator, const_iterator> equal_range(
369 const key_type& key) const {
370 auto result = map_.equal_range(key);
371 return std::make_pair(const_iterator(result.first),
372 const_iterator(result.second));
373 }
374
375 iterator lower_bound(const key_type& key) {
376 return iterator(map_.lower_bound(key));
377 }
378
379 const_iterator lower_bound(const key_type& key) const {
380 return const_iterator(map_.lower_bound(key));
381 }
382
383 iterator upper_bound(const key_type& key) {
384 return iterator(map_.upper_bound(key));
385 }
386
387 const_iterator upper_bound(const key_type& key) const {
388 return const_iterator(map_.upper_bound(key));
389 }
390
391 private:
394 template <typename K, typename... Args>
395 [[nodiscard]] std::optional<std::pair<iterator, bool>> TryEmplaceImpl(
396 K&& key, Args&&... args) {
397 // Perform lookup before allocation to avoid unnecessary heap usage.
398 if (auto it = map_.find(key); it != map_.end()) {
399 return std::make_pair(iterator(it), false);
400 }
401 MapItem* item = allocator_->template New<MapItem>(
402 std::forward<K>(key), std::forward<Args>(args)...);
403 if (item == nullptr) {
404 return std::nullopt;
405 }
406 auto [item_it, inserted] = map_.insert(*item);
407 PW_ASSERT(inserted);
408 return std::make_pair(iterator(item_it), true);
409 }
410
411 Allocator* allocator_;
412 Map map_;
413};
414
415} // namespace pw
Definition: allocator.h:45
Definition: dynamic_map.h:46
Definition: intrusive_map.h:71
void Delete(T *ptr)
Definition: deallocator.h:99
MapItem(K &&key, Args &&... args)
Definition: dynamic_map.h:58
iterator erase(iterator pos)
Removes the element at pos and deallocates the node.
Definition: dynamic_map.h:297
constexpr allocator_type & get_allocator() const
Returns the allocator used by this map.
Definition: dynamic_map.h:185
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:222
constexpr DynamicMap(Allocator &allocator) noexcept
Definition: dynamic_map.h:155
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:336
iterator upper_bound(const key_type &key)
Definition: intrusive_map.h:321
mapped_type & at(const key_type &key)
Definition: dynamic_map.h:210
std::pair< iterator, bool > insert(T &item)
Definition: intrusive_map.h:241
DynamicMap & operator=(DynamicMap &&other) noexcept
Definition: dynamic_map.h:172
iterator erase(T &item)
Definition: intrusive_map.h:264
DynamicMap(DynamicMap &&other) noexcept
Definition: dynamic_map.h:165
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:319
void swap(DynamicMap &other)
Swaps the contents and allocators of two maps. No allocations occur.
Definition: dynamic_map.h:329
constexpr size_t max_size() const noexcept
Definition: intrusive_map.h:225
std::optional< insert_return_type > try_insert(const value_type &value)
Definition: dynamic_map.h:244
std::optional< std::pair< iterator, bool > > try_emplace(K &&key, Args &&... args)
Definition: dynamic_map.h:282
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:289
void clear()
Removes all elements from the map and deallocates each node.
Definition: dynamic_map.h:233
iterator erase(iterator first, iterator last)
Removes elements in the range [first, last).
Definition: dynamic_map.h:309
insert_return_type insert(const value_type &value)
Inserts a value into the map. Crashes on allocation failure.
Definition: dynamic_map.h:255
void insert(InputIt first, InputIt last)
Inserts a range of elements. Crashes on allocation failure.
Definition: dynamic_map.h:266
DynamicMap(const DynamicMap &)=delete
The Pigweed namespace.
Definition: alignment.h:27