• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <ftl/initializer_list.h>
20 #include <ftl/small_vector.h>
21 
22 #include <algorithm>
23 #include <functional>
24 #include <optional>
25 #include <type_traits>
26 #include <utility>
27 
28 namespace android::ftl {
29 
30 // Associative container with unique, unordered keys. Unlike std::unordered_map, key-value pairs are
31 // stored in contiguous storage for cache efficiency. The map is allocated statically until its size
32 // exceeds N, at which point mappings are relocated to dynamic memory. The try_emplace operation has
33 // a non-standard analogue try_replace that destructively emplaces. The API also defines an in-place
34 // counterpart to insert_or_assign: emplace_or_replace. Lookup is done not via a subscript operator,
35 // but immutable getters that can optionally transform the value.
36 //
37 // SmallMap<K, V, 0> unconditionally allocates on the heap.
38 //
39 // Example usage:
40 //
41 //   ftl::SmallMap<int, std::string, 3> map;
42 //   assert(map.empty());
43 //   assert(!map.dynamic());
44 //
45 //   map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
46 //   assert(map.size() == 3u);
47 //   assert(!map.dynamic());
48 //
49 //   assert(map.contains(123));
50 //   assert(map.get(42, [](const std::string& s) { return s.size(); }) == 3u);
51 //
52 //   const auto opt = map.get(-1);
53 //   assert(opt);
54 //
55 //   std::string& ref = *opt;
56 //   assert(ref.empty());
57 //   ref = "xyz";
58 //
59 //   map.emplace_or_replace(0, "vanilla", 2u, 3u);
60 //   assert(map.dynamic());
61 //
62 //   assert(map == SmallMap(ftl::init::map(-1, "xyz")(0, "nil")(42, "???")(123, "abc")));
63 //
64 template <typename K, typename V, std::size_t N, typename KeyEqual = std::equal_to<K>>
65 class SmallMap final {
66   using Map = SmallVector<std::pair<const K, V>, N>;
67 
68   template <typename, typename, std::size_t, typename>
69   friend class SmallMap;
70 
71  public:
72   using key_type = K;
73   using mapped_type = V;
74 
75   using value_type = typename Map::value_type;
76   using size_type = typename Map::size_type;
77   using difference_type = typename Map::difference_type;
78 
79   using reference = typename Map::reference;
80   using iterator = typename Map::iterator;
81 
82   using const_reference = typename Map::const_reference;
83   using const_iterator = typename Map::const_iterator;
84 
85   // Creates an empty map.
86   SmallMap() = default;
87 
88   // Constructs at most N key-value pairs in place by forwarding per-pair constructor arguments.
89   // The template arguments K, V, and N are inferred using the deduction guide defined below.
90   // The syntax for listing pairs is as follows:
91   //
92   //   ftl::SmallMap map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
93   //   static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, std::string, 3>>);
94   //
95   // The types of the key and value are deduced if the first pair contains exactly two arguments:
96   //
97   //   ftl::SmallMap map = ftl::init::map(0, 'a')(1, 'b')(2, 'c');
98   //   static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, char, 3>>);
99   //
100   template <typename U, std::size_t... Sizes, typename... Types>
SmallMap(InitializerList<U,std::index_sequence<Sizes...>,Types...> && list)101   SmallMap(InitializerList<U, std::index_sequence<Sizes...>, Types...>&& list)
102       : map_(std::move(list)) {
103     deduplicate();
104   }
105 
106   // Copies or moves key-value pairs from a convertible map.
107   template <typename Q, typename W, std::size_t M, typename E>
SmallMap(SmallMap<Q,W,M,E> other)108   SmallMap(SmallMap<Q, W, M, E> other) : map_(std::move(other.map_)) {}
109 
max_size()110   size_type max_size() const { return map_.max_size(); }
size()111   size_type size() const { return map_.size(); }
empty()112   bool empty() const { return map_.empty(); }
113 
114   // Returns whether the map is backed by static or dynamic storage.
dynamic()115   bool dynamic() const { return map_.dynamic(); }
116 
begin()117   iterator begin() { return map_.begin(); }
begin()118   const_iterator begin() const { return cbegin(); }
cbegin()119   const_iterator cbegin() const { return map_.cbegin(); }
120 
end()121   iterator end() { return map_.end(); }
end()122   const_iterator end() const { return cend(); }
cend()123   const_iterator cend() const { return map_.cend(); }
124 
125   // Returns whether a mapping exists for the given key.
contains(const key_type & key)126   bool contains(const key_type& key) const {
127     return get(key, [](const mapped_type&) {});
128   }
129 
130   // Returns a reference to the value for the given key, or std::nullopt if the key was not found.
131   //
132   //   ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
133   //
134   //   const auto opt = map.get('c');
135   //   assert(opt == 'C');
136   //
137   //   char d = 'd';
138   //   const auto ref = map.get('d').value_or(std::ref(d));
139   //   ref.get() = 'D';
140   //   assert(d == 'D');
141   //
142   auto get(const key_type& key) const -> std::optional<std::reference_wrapper<const mapped_type>> {
143     return get(key, [](const mapped_type& v) { return std::cref(v); });
144   }
145 
146   auto get(const key_type& key) -> std::optional<std::reference_wrapper<mapped_type>> {
147     return get(key, [](mapped_type& v) { return std::ref(v); });
148   }
149 
150   // Returns the result R of a unary operation F on (a constant or mutable reference to) the value
151   // for the given key, or std::nullopt if the key was not found. If F has a return type of void,
152   // then the Boolean result indicates whether the key was found.
153   //
154   //   ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
155   //
156   //   assert(map.get('c', [](char c) { return std::toupper(c); }) == 'Z');
157   //   assert(map.get('c', [](char& c) { c = std::toupper(c); }));
158   //
159   template <typename F, typename R = std::invoke_result_t<F, const mapped_type&>>
160   auto get(const key_type& key, F f) const
161       -> std::conditional_t<std::is_void_v<R>, bool, std::optional<R>> {
162     for (auto& [k, v] : *this) {
163       if (KeyEqual{}(k, key)) {
164         if constexpr (std::is_void_v<R>) {
165           f(v);
166           return true;
167         } else {
168           return f(v);
169         }
170       }
171     }
172 
173     return {};
174   }
175 
176   template <typename F>
get(const key_type & key,F f)177   auto get(const key_type& key, F f) {
178     return std::as_const(*this).get(
179         key, [&f](const mapped_type& v) { return f(const_cast<mapped_type&>(v)); });
180   }
181 
182   // Returns an iterator to an existing mapping for the given key, or the end() iterator otherwise.
find(const key_type & key)183   const_iterator find(const key_type& key) const { return const_cast<SmallMap&>(*this).find(key); }
find(const key_type & key)184   iterator find(const key_type& key) { return find(key, begin()); }
185 
186   // Inserts a mapping unless it exists. Returns an iterator to the inserted or existing mapping,
187   // and whether the mapping was inserted.
188   //
189   // On emplace, if the map reaches its static or dynamic capacity, then all iterators are
190   // invalidated. Otherwise, only the end() iterator is invalidated.
191   //
192   template <typename... Args>
try_emplace(const key_type & key,Args &&...args)193   std::pair<iterator, bool> try_emplace(const key_type& key, Args&&... args) {
194     if (const auto it = find(key); it != end()) {
195       return {it, false};
196     }
197 
198     auto& ref = map_.emplace_back(std::piecewise_construct, std::forward_as_tuple(key),
199                                   std::forward_as_tuple(std::forward<Args>(args)...));
200     return {&ref, true};
201   }
202 
203   // Replaces a mapping if it exists, and returns an iterator to it. Returns the end() iterator
204   // otherwise.
205   //
206   // The value is replaced via move constructor, so type V does not need to define copy/move
207   // assignment, e.g. its data members may be const.
208   //
209   // The arguments may directly or indirectly refer to the mapping being replaced.
210   //
211   // Iterators to the replaced mapping point to its replacement, and others remain valid.
212   //
213   template <typename... Args>
try_replace(const key_type & key,Args &&...args)214   iterator try_replace(const key_type& key, Args&&... args) {
215     const auto it = find(key);
216     if (it == end()) return it;
217     map_.replace(it, std::piecewise_construct, std::forward_as_tuple(key),
218                  std::forward_as_tuple(std::forward<Args>(args)...));
219     return it;
220   }
221 
222   // In-place counterpart of std::unordered_map's insert_or_assign. Returns true on emplace, or
223   // false on replace.
224   //
225   // The value is emplaced and replaced via move constructor, so type V does not need to define
226   // copy/move assignment, e.g. its data members may be const.
227   //
228   // On emplace, if the map reaches its static or dynamic capacity, then all iterators are
229   // invalidated. Otherwise, only the end() iterator is invalidated. On replace, iterators
230   // to the replaced mapping point to its replacement, and others remain valid.
231   //
232   template <typename... Args>
emplace_or_replace(const key_type & key,Args &&...args)233   std::pair<iterator, bool> emplace_or_replace(const key_type& key, Args&&... args) {
234     const auto [it, ok] = try_emplace(key, std::forward<Args>(args)...);
235     if (ok) return {it, ok};
236     map_.replace(it, std::piecewise_construct, std::forward_as_tuple(key),
237                  std::forward_as_tuple(std::forward<Args>(args)...));
238     return {it, ok};
239   }
240 
241   // Removes a mapping if it exists, and returns whether it did.
242   //
243   // The last() and end() iterators, as well as those to the erased mapping, are invalidated.
244   //
erase(const key_type & key)245   bool erase(const key_type& key) { return erase(key, begin()); }
246 
247   // Removes all mappings.
248   //
249   // All iterators are invalidated.
250   //
clear()251   void clear() { map_.clear(); }
252 
253  private:
find(const key_type & key,iterator first)254   iterator find(const key_type& key, iterator first) {
255     return std::find_if(first, end(),
256                         [&key](const auto& pair) { return KeyEqual{}(pair.first, key); });
257   }
258 
erase(const key_type & key,iterator first)259   bool erase(const key_type& key, iterator first) {
260     const auto it = find(key, first);
261     if (it == end()) return false;
262     map_.unstable_erase(it);
263     return true;
264   }
265 
deduplicate()266   void deduplicate() {
267     for (auto it = begin(); it != end();) {
268       if (const auto key = it->first; ++it != end()) {
269         while (erase(key, it));
270       }
271     }
272   }
273 
274   Map map_;
275 };
276 
277 // Deduction guide for in-place constructor.
278 template <typename K, typename V, typename E, std::size_t... Sizes, typename... Types>
279 SmallMap(InitializerList<KeyValue<K, V, E>, std::index_sequence<Sizes...>, Types...>&&)
280     -> SmallMap<K, V, sizeof...(Sizes), E>;
281 
282 // Returns whether the key-value pairs of two maps are equal.
283 template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M, typename E>
284 bool operator==(const SmallMap<K, V, N, E>& lhs, const SmallMap<Q, W, M, E>& rhs) {
285   if (lhs.size() != rhs.size()) return false;
286 
287   for (const auto& [k, v] : lhs) {
288     const auto& lv = v;
289     if (!rhs.get(k, [&lv](const auto& rv) { return lv == rv; }).value_or(false)) {
290       return false;
291     }
292   }
293 
294   return true;
295 }
296 
297 // TODO: Remove in C++20.
298 template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M, typename E>
299 inline bool operator!=(const SmallMap<K, V, N, E>& lhs, const SmallMap<Q, W, M, E>& rhs) {
300   return !(lhs == rhs);
301 }
302 
303 }  // namespace android::ftl
304