1 // Copyright 2012 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef BASE_CONTAINERS_SMALL_MAP_H_ 6 #define BASE_CONTAINERS_SMALL_MAP_H_ 7 8 #include <stddef.h> 9 10 #include <array> 11 #include <limits> 12 #include <map> 13 #include <memory> 14 #include <new> 15 #include <type_traits> 16 #include <utility> 17 18 #include "base/check.h" 19 #include "base/check_op.h" 20 #include "base/containers/adapters.h" 21 #include "base/containers/span.h" 22 #include "base/memory/stack_allocated.h" 23 #include "base/numerics/safe_conversions.h" 24 #include "base/types/to_address.h" 25 26 inline constexpr size_t kUsingFullMapSentinel = 27 std::numeric_limits<size_t>::max(); 28 29 namespace base { 30 31 // small_map is a container with a std::map-like interface. It starts out backed 32 // by an unsorted array but switches to some other container type if it grows 33 // beyond this fixed size. 34 // 35 // Please see //base/containers/README.md for an overview of which container 36 // to select. 37 // 38 // PROS 39 // 40 // - Good memory locality and low overhead for smaller maps. 41 // - Handles large maps without the degenerate performance of flat_map. 42 // 43 // CONS 44 // 45 // - Larger code size than the alternatives. 46 // 47 // IMPORTANT NOTES 48 // 49 // - Iterators are invalidated across mutations. 50 // 51 // DETAILS 52 // 53 // base::small_map will pick up the comparator from the underlying map type. In 54 // std::map only a "less" operator is defined, which requires us to do two 55 // comparisons per element when doing the brute-force search in the simple 56 // array. std::unordered_map has a key_equal function which will be used. 57 // 58 // We define default overrides for the common map types to avoid this 59 // double-compare, but you should be aware of this if you use your own operator< 60 // for your map and supply your own version of == to the small_map. You can use 61 // regular operator== by just doing: 62 // 63 // base::small_map<std::map<MyKey, MyValue>, 4, std::equal_to<KyKey>> 64 // 65 // 66 // USAGE 67 // ----- 68 // 69 // NormalMap: The map type to fall back to. This also defines the key and value 70 // types for the small_map. 71 // kArraySize: The size of the initial array of results. This will be allocated 72 // with the small_map object rather than separately on the heap. 73 // Once the map grows beyond this size, the map type will be used 74 // instead. 75 // EqualKey: A functor which tests two keys for equality. If the wrapped map 76 // type has a "key_equal" member (unordered_map does), then that will 77 // be used by default. If the wrapped map type has a strict weak 78 // ordering "key_compare" (std::map does), that will be used to 79 // implement equality by default. 80 // MapInit: A functor that takes a NormalMap* and uses it to initialize the map. 81 // This functor will be called at most once per small_map, when the map 82 // exceeds the threshold of kArraySize and we are about to copy values 83 // from the array to the map. The functor *must* initialize the 84 // NormalMap* argument with placement new, since after it runs we 85 // assume that the NormalMap has been initialized. 86 // 87 // Example: 88 // base::small_map<std::map<string, int>> days; 89 // days["sunday" ] = 0; 90 // days["monday" ] = 1; 91 // days["tuesday" ] = 2; 92 // days["wednesday"] = 3; 93 // days["thursday" ] = 4; 94 // days["friday" ] = 5; 95 // days["saturday" ] = 6; 96 97 namespace internal { 98 99 template <typename NormalMap> 100 class small_map_default_init { 101 public: operator()102 void operator()(NormalMap* map) const { std::construct_at(map); } 103 }; 104 105 // has_key_equal<M>::value is true iff there exists a type M::key_equal. This is 106 // used to dispatch to one of the select_equal_key<> metafunctions below. 107 template <typename M> 108 struct has_key_equal { 109 typedef char sml; // "small" is sometimes #defined so we use an abbreviation. 110 typedef struct { char dummy[2]; } big; 111 // Two functions, one accepts types that have a key_equal member, and one that 112 // accepts anything. They each return a value of a different size, so we can 113 // determine at compile-time which function would have been called. 114 template <typename U> static big test(typename U::key_equal*); 115 template <typename> static sml test(...); 116 // Determines if M::key_equal exists by looking at the size of the return 117 // type of the compiler-chosen test() function. 118 static const bool value = (sizeof(test<M>(0)) == sizeof(big)); 119 }; 120 template <typename M> const bool has_key_equal<M>::value; 121 122 // Base template used for map types that do NOT have an M::key_equal member, 123 // e.g., std::map<>. These maps have a strict weak ordering comparator rather 124 // than an equality functor, so equality will be implemented in terms of that 125 // comparator. 126 // 127 // There's a partial specialization of this template below for map types that do 128 // have an M::key_equal member. 129 template <typename M, bool has_key_equal_value> 130 struct select_equal_key { 131 struct equal_key { operatorselect_equal_key::equal_key132 bool operator()(const typename M::key_type& left, 133 const typename M::key_type& right) { 134 // Implements equality in terms of a strict weak ordering comparator. 135 typename M::key_compare comp; 136 return !comp(left, right) && !comp(right, left); 137 } 138 }; 139 }; 140 141 // Partial template specialization handles case where M::key_equal exists, e.g., 142 // unordered_map<>. 143 template <typename M> 144 struct select_equal_key<M, true> { 145 typedef typename M::key_equal equal_key; 146 }; 147 148 } // namespace internal 149 150 template <typename NormalMap, 151 size_t kArraySize = 4, 152 typename EqualKey = typename internal::select_equal_key< 153 NormalMap, 154 internal::has_key_equal<NormalMap>::value>::equal_key, 155 typename MapInit = internal::small_map_default_init<NormalMap>> 156 class small_map { 157 static_assert(kArraySize > 0, "Initial size must be greater than 0"); 158 static_assert(kArraySize != kUsingFullMapSentinel, 159 "Initial size out of range"); 160 161 public: 162 using key_type = NormalMap::key_type; 163 using data_type = NormalMap::mapped_type; 164 using mapped_type = NormalMap::mapped_type; 165 using value_type = NormalMap::value_type; 166 using key_equal = EqualKey; 167 168 constexpr small_map() : functor_(MapInit()) { InitEmpty(); } 169 170 constexpr explicit small_map(const MapInit& functor) : functor_(functor) { 171 InitEmpty(); 172 } 173 174 // Allow copy-constructor and assignment, since STL allows them too. 175 constexpr small_map(const small_map& src) { 176 // size_ and functor_ are initted in InitFrom() 177 InitFrom(src); 178 } 179 180 constexpr void operator=(const small_map& src) { 181 if (&src == this) return; 182 183 // This is not optimal. If src and dest are both using the small array, we 184 // could skip the teardown and reconstruct. One problem to be resolved is 185 // that the value_type itself is pair<const K, V>, and const K is not 186 // assignable. 187 Destroy(); 188 InitFrom(src); 189 } 190 191 ~small_map() { Destroy(); } 192 193 // The elements in the inline array storage. They are held in a union so that 194 // they can be constructed lazily as they are inserted, and can be destroyed 195 // when they are erased. 196 union ArrayElement { 197 ArrayElement() {} 198 ~ArrayElement() {} 199 200 value_type value; 201 }; 202 203 class const_iterator; 204 205 class iterator { 206 STACK_ALLOCATED(); 207 208 using map_iterator = NormalMap::iterator; 209 using array_iterator = span<ArrayElement>::iterator; 210 211 public: 212 using iterator_category = map_iterator::iterator_category; 213 using value_type = map_iterator::value_type; 214 using difference_type = map_iterator::difference_type; 215 using pointer = map_iterator::pointer; 216 using reference = map_iterator::reference; 217 218 iterator() = default; 219 220 constexpr iterator& operator++() { 221 if (has_array_iter()) { 222 ++array_iter_; 223 } else { 224 ++map_iter_; 225 } 226 return *this; 227 } 228 229 constexpr iterator operator++(int /*unused*/) { 230 iterator result(*this); 231 ++(*this); 232 return result; 233 } 234 235 constexpr value_type* operator->() const { 236 return has_array_iter() ? std::addressof(array_iter_->value) 237 : std::addressof(*map_iter_); 238 } 239 240 constexpr value_type& operator*() const { 241 return has_array_iter() ? array_iter_->value : *map_iter_; 242 } 243 244 constexpr bool operator==(const iterator& other) const { 245 if (has_array_iter()) { 246 return array_iter_ == other.array_iter_; 247 } else { 248 return !other.has_array_iter() && map_iter_ == other.map_iter_; 249 } 250 } 251 252 private: 253 friend class small_map; 254 friend class const_iterator; 255 constexpr explicit iterator(const array_iterator& init) 256 : array_iter_(init) {} 257 constexpr explicit iterator(const map_iterator& init) : map_iter_(init) {} 258 259 constexpr bool has_array_iter() const { 260 return base::to_address(array_iter_) != nullptr; 261 } 262 263 array_iterator array_iter_; 264 map_iterator map_iter_; 265 }; 266 267 class const_iterator { 268 STACK_ALLOCATED(); 269 270 using map_iterator = NormalMap::const_iterator; 271 using array_iterator = span<const ArrayElement>::iterator; 272 273 public: 274 using iterator_category = map_iterator::iterator_category; 275 using value_type = map_iterator::value_type; 276 using difference_type = map_iterator::difference_type; 277 using pointer = map_iterator::pointer; 278 using reference = map_iterator::reference; 279 280 const_iterator() = default; 281 282 // Non-explicit constructor lets us convert regular iterators to const 283 // iterators. 284 constexpr const_iterator(const iterator& other) 285 : array_iter_(other.array_iter_), map_iter_(other.map_iter_) {} 286 287 constexpr const_iterator& operator++() { 288 if (has_array_iter()) { 289 ++array_iter_; 290 } else { 291 ++map_iter_; 292 } 293 return *this; 294 } 295 296 constexpr const_iterator operator++(int /*unused*/) { 297 const_iterator result(*this); 298 ++(*this); 299 return result; 300 } 301 302 constexpr const value_type* operator->() const { 303 return has_array_iter() ? std::addressof(array_iter_->value) 304 : std::addressof(*map_iter_); 305 } 306 307 constexpr const value_type& operator*() const { 308 return has_array_iter() ? array_iter_->value : *map_iter_; 309 } 310 311 constexpr bool operator==(const const_iterator& other) const { 312 if (has_array_iter()) { 313 return array_iter_ == other.array_iter_; 314 } 315 return !other.has_array_iter() && map_iter_ == other.map_iter_; 316 } 317 318 private: 319 friend class small_map; 320 constexpr explicit const_iterator(const array_iterator& init) 321 : array_iter_(init) {} 322 constexpr explicit const_iterator(const map_iterator& init) 323 : map_iter_(init) {} 324 325 constexpr bool has_array_iter() const { 326 return base::to_address(array_iter_) != nullptr; 327 } 328 329 array_iterator array_iter_; 330 map_iterator map_iter_; 331 }; 332 333 constexpr iterator find(const key_type& key) { 334 key_equal compare; 335 336 if (UsingFullMap()) { 337 return iterator(map()->find(key)); 338 } 339 340 span<ArrayElement> r = sized_array_span(); 341 auto it = r.begin(); 342 for (; it != r.end(); ++it) { 343 if (compare(it->value.first, key)) { 344 return iterator(it); 345 } 346 } 347 return iterator(it); 348 } 349 350 constexpr const_iterator find(const key_type& key) const { 351 key_equal compare; 352 353 if (UsingFullMap()) { 354 return const_iterator(map()->find(key)); 355 } 356 357 span<const ArrayElement> r = sized_array_span(); 358 auto it = r.begin(); 359 for (; it != r.end(); ++it) { 360 if (compare(it->value.first, key)) { 361 return const_iterator(it); 362 } 363 } 364 return const_iterator(it); 365 } 366 367 // Invalidates iterators. 368 constexpr data_type& operator[](const key_type& key) 369 requires(std::is_default_constructible_v<data_type>) 370 { 371 key_equal compare; 372 373 if (UsingFullMap()) { 374 return map_[key]; 375 } 376 377 // Search backwards to favor recently-added elements. 378 span<ArrayElement> r = sized_array_span(); 379 for (ArrayElement& e : Reversed(r)) { 380 if (compare(e.value.first, key)) { 381 return e.value.second; 382 } 383 } 384 385 if (size_ == kArraySize) { 386 ConvertToRealMap(); 387 return map_[key]; 388 } 389 390 ArrayElement& e = array_[size_++]; 391 std::construct_at(std::addressof(e.value), key, data_type()); 392 return e.value.second; 393 } 394 395 // Invalidates iterators. 396 constexpr std::pair<iterator, bool> insert(const value_type& x) { 397 key_equal compare; 398 399 if (UsingFullMap()) { 400 auto [map_iter, inserted] = map_.insert(x); 401 return std::make_pair(iterator(map_iter), inserted); 402 } 403 404 span<ArrayElement> r = sized_array_span(); 405 for (auto it = r.begin(); it != r.end(); ++it) { 406 if (compare(it->value.first, x.first)) { 407 return std::make_pair(iterator(it), false); 408 } 409 } 410 411 if (size_ == kArraySize) { 412 ConvertToRealMap(); // Invalidates all iterators! 413 auto [map_iter, inserted] = map_.insert(x); 414 return std::make_pair(iterator(map_iter), inserted); 415 } 416 417 ArrayElement& e = array_[size_++]; 418 std::construct_at(std::addressof(e.value), x); 419 return std::make_pair(iterator(sized_array_span().end() - 1u), true); 420 } 421 422 // Invalidates iterators. 423 template <class InputIterator> 424 constexpr void insert(InputIterator f, InputIterator l) { 425 while (f != l) { 426 insert(*f); 427 ++f; 428 } 429 } 430 431 // Invalidates iterators. 432 template <typename... Args> 433 constexpr std::pair<iterator, bool> emplace(Args&&... args) { 434 key_equal compare; 435 436 if (UsingFullMap()) { 437 auto [map_iter, inserted] = map_.emplace(std::forward<Args>(args)...); 438 return std::make_pair(iterator(map_iter), inserted); 439 } 440 441 value_type x(std::forward<Args>(args)...); 442 span<ArrayElement> r = sized_array_span(); 443 for (auto it = r.begin(); it != r.end(); ++it) { 444 if (compare(it->value.first, x.first)) { 445 return std::make_pair(iterator(it), false); 446 } 447 } 448 449 if (size_ == kArraySize) { 450 ConvertToRealMap(); // Invalidates all iterators! 451 auto [map_iter, inserted] = map_.emplace(std::move(x)); 452 return std::make_pair(iterator(map_iter), inserted); 453 } 454 455 ArrayElement& p = array_[size_++]; 456 std::construct_at(std::addressof(p.value), std::move(x)); 457 return std::make_pair(iterator(sized_array_span().end() - 1u), true); 458 } 459 460 constexpr iterator begin() { 461 return UsingFullMap() ? iterator(map_.begin()) 462 : iterator(sized_array_span().begin()); 463 } 464 465 constexpr const_iterator begin() const { 466 return UsingFullMap() ? const_iterator(map_.begin()) 467 : const_iterator(sized_array_span().begin()); 468 } 469 470 constexpr iterator end() { 471 return UsingFullMap() ? iterator(map_.end()) 472 : iterator(sized_array_span().end()); 473 } 474 475 constexpr const_iterator end() const { 476 return UsingFullMap() ? const_iterator(map_.end()) 477 : const_iterator(sized_array_span().end()); 478 } 479 480 constexpr void clear() { 481 if (UsingFullMap()) { 482 // Make the array active in the union. 483 map_.~NormalMap(); 484 std::construct_at(&array_); 485 } else { 486 for (ArrayElement& e : sized_array_span()) { 487 e.value.~value_type(); 488 } 489 } 490 size_ = 0u; 491 } 492 493 // Invalidates iterators. Returns iterator following the last removed element. 494 constexpr iterator erase(const iterator& position) { 495 if (UsingFullMap()) { 496 return iterator(map_.erase(position.map_iter_)); 497 } 498 499 auto erase_pos = position.array_iter_; 500 auto last_pos = sized_array_span().end() - 1u; 501 502 if (erase_pos == last_pos) { 503 erase_pos->value.~value_type(); 504 --size_; 505 return end(); 506 } else { 507 ptrdiff_t index = std::ranges::distance(begin().array_iter_, erase_pos); 508 erase_pos->value.~value_type(); 509 std::construct_at(std::addressof(erase_pos->value), 510 std::move(last_pos->value)); 511 last_pos->value.~value_type(); 512 --size_; 513 return iterator(sized_array_span().begin() + index); 514 } 515 } 516 517 constexpr size_t erase(const key_type& key) { 518 iterator iter = find(key); 519 if (iter == end()) { 520 return 0u; 521 } 522 erase(iter); 523 return 1u; 524 } 525 526 constexpr size_t count(const key_type& key) const { 527 return (find(key) == end()) ? 0u : 1u; 528 } 529 530 constexpr size_t size() const { return UsingFullMap() ? map_.size() : size_; } 531 532 constexpr bool empty() const { 533 return UsingFullMap() ? map_.empty() : size_ == 0u; 534 } 535 536 // Returns true if we have fallen back to using the underlying map 537 // representation. 538 constexpr bool UsingFullMap() const { return size_ == kUsingFullMapSentinel; } 539 540 constexpr NormalMap* map() { 541 CHECK(UsingFullMap()); 542 return &map_; 543 } 544 545 constexpr const NormalMap* map() const { 546 CHECK(UsingFullMap()); 547 return &map_; 548 } 549 550 private: 551 // When `size_ == kUsingFullMapSentinel`, we have switched storage strategies 552 // from `array_[kArraySize] to `NormalMap map_`. See ConvertToRealMap and 553 // UsingFullMap. 554 size_t size_ = 0u; 555 556 MapInit functor_; 557 558 // We want to call constructors and destructors manually, but we don't want 559 // to allocate and deallocate the memory used for them separately. Since 560 // array_ and map_ are mutually exclusive, we'll put them in a union. 561 using ArrayMap = std::array<ArrayElement, kArraySize>; 562 union { 563 ArrayMap array_; 564 NormalMap map_; 565 }; 566 567 constexpr span<ArrayElement> sized_array_span() { 568 CHECK(!UsingFullMap()); 569 return span(array_).first(size_); 570 } 571 constexpr span<const ArrayElement> sized_array_span() const { 572 CHECK(!UsingFullMap()); 573 return span(array_).first(size_); 574 } 575 576 constexpr void ConvertToRealMap() { 577 CHECK_EQ(size_, kArraySize); 578 579 std::array<ArrayElement, kArraySize> temp_array; 580 581 // Move the current elements into a temporary array. 582 for (size_t i = 0u; i < kArraySize; ++i) { 583 ArrayElement& e = temp_array[i]; 584 std::construct_at(std::addressof(e.value), std::move(array_[i].value)); 585 array_[i].value.~value_type(); 586 } 587 588 // Make the map active in the union. 589 size_ = kUsingFullMapSentinel; 590 array_.~ArrayMap(); 591 functor_(&map_); 592 593 // Insert elements into it. 594 for (ArrayElement& e : temp_array) { 595 map_.insert(std::move(e.value)); 596 e.value.~value_type(); 597 } 598 } 599 600 // Helpers for constructors and destructors. 601 constexpr void InitEmpty() { 602 // Make the array active in the union. 603 std::construct_at(&array_); 604 } 605 constexpr void InitFrom(const small_map& src) { 606 functor_ = src.functor_; 607 size_ = src.size_; 608 if (src.UsingFullMap()) { 609 // Make the map active in the union. 610 functor_(&map_); 611 map_ = src.map_; 612 } else { 613 // Make the array active in the union. 614 std::construct_at(&array_); 615 for (size_t i = 0u; i < size_; ++i) { 616 ArrayElement& e = array_[i]; 617 std::construct_at(std::addressof(e.value), src.array_[i].value); 618 } 619 } 620 } 621 622 constexpr void Destroy() { 623 if (UsingFullMap()) { 624 map_.~NormalMap(); 625 } else { 626 for (size_t i = 0u; i < size_; ++i) { 627 array_[i].value.~value_type(); 628 } 629 array_.~ArrayMap(); 630 } 631 } 632 }; 633 634 } // namespace base 635 636 #endif // BASE_CONTAINERS_SMALL_MAP_H_ 637