• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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