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