blob: 5217e760644192055ef4ff1b54d25645d54ba882 [file] [log] [blame]
Dominik Laskowskic4b91462020-11-02 13:37:25 -08001/*
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
Dominik Laskowski5444fc82020-11-24 13:41:10 -080019#include <ftl/initializer_list.h>
20#include <ftl/small_vector.h>
Dominik Laskowskic4b91462020-11-02 13:37:25 -080021
Dominik Laskowskidda9bba2021-02-03 18:56:00 -080022#include <algorithm>
Dominik Laskowskic4b91462020-11-02 13:37:25 -080023#include <functional>
24#include <optional>
25#include <type_traits>
26#include <utility>
27
28namespace 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
Dominik Laskowskidda9bba2021-02-03 18:56:00 -080032// 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.
Dominik Laskowskic4b91462020-11-02 13:37:25 -080036//
37// SmallMap<K, V, 0> unconditionally allocates on the heap.
38//
39// Example usage:
40//
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080041// ftl::SmallMap<int, std::string, 3> map;
42// assert(map.empty());
43// assert(!map.dynamic());
Dominik Laskowskic4b91462020-11-02 13:37:25 -080044//
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080045// map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
46// assert(map.size() == 3u);
47// assert(!map.dynamic());
Dominik Laskowskic4b91462020-11-02 13:37:25 -080048//
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080049// assert(map.contains(123));
Dominik Laskowskidda9bba2021-02-03 18:56:00 -080050// assert(map.get(42, [](const std::string& s) { return s.size(); }) == 3u);
Dominik Laskowskic4b91462020-11-02 13:37:25 -080051//
Dominik Laskowskidda9bba2021-02-03 18:56:00 -080052// const auto opt = map.get(-1);
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080053// assert(opt);
Dominik Laskowskic4b91462020-11-02 13:37:25 -080054//
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080055// std::string& ref = *opt;
56// assert(ref.empty());
57// ref = "xyz";
Dominik Laskowskic4b91462020-11-02 13:37:25 -080058//
Dominik Laskowskidda9bba2021-02-03 18:56:00 -080059// 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")));
Dominik Laskowskic4b91462020-11-02 13:37:25 -080063//
Dominik Laskowski18748962021-09-26 18:47:58 -070064template <typename K, typename V, std::size_t N, typename KeyEqual = std::equal_to<K>>
Dominik Laskowskic4b91462020-11-02 13:37:25 -080065class SmallMap final {
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080066 using Map = SmallVector<std::pair<const K, V>, N>;
Dominik Laskowskic4b91462020-11-02 13:37:25 -080067
Dominik Laskowski206996f2022-01-20 13:21:57 -080068 template <typename, typename, std::size_t, typename>
69 friend class SmallMap;
70
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080071 public:
72 using key_type = K;
73 using mapped_type = V;
Dominik Laskowskic4b91462020-11-02 13:37:25 -080074
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080075 using value_type = typename Map::value_type;
76 using size_type = typename Map::size_type;
77 using difference_type = typename Map::difference_type;
Dominik Laskowskic4b91462020-11-02 13:37:25 -080078
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080079 using reference = typename Map::reference;
80 using iterator = typename Map::iterator;
Dominik Laskowskic4b91462020-11-02 13:37:25 -080081
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080082 using const_reference = typename Map::const_reference;
83 using const_iterator = typename Map::const_iterator;
Dominik Laskowskic4b91462020-11-02 13:37:25 -080084
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080085 // Creates an empty map.
86 SmallMap() = default;
Dominik Laskowskic4b91462020-11-02 13:37:25 -080087
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080088 // 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, '?');
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080093 // static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, std::string, 3>>);
Dominik Laskowskie21dbed2020-12-04 20:51:43 -080094 //
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>
101 SmallMap(InitializerList<U, std::index_sequence<Sizes...>, Types...>&& list)
102 : map_(std::move(list)) {
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800103 deduplicate();
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800104 }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800105
Dominik Laskowski206996f2022-01-20 13:21:57 -0800106 // Copies or moves key-value pairs from a convertible map.
107 template <typename Q, typename W, std::size_t M, typename E>
108 SmallMap(SmallMap<Q, W, M, E> other) : map_(std::move(other.map_)) {}
109
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800110 size_type max_size() const { return map_.max_size(); }
111 size_type size() const { return map_.size(); }
112 bool empty() const { return map_.empty(); }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800113
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800114 // Returns whether the map is backed by static or dynamic storage.
115 bool dynamic() const { return map_.dynamic(); }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800116
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800117 iterator begin() { return map_.begin(); }
118 const_iterator begin() const { return cbegin(); }
119 const_iterator cbegin() const { return map_.cbegin(); }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800120
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800121 iterator end() { return map_.end(); }
122 const_iterator end() const { return cend(); }
123 const_iterator cend() const { return map_.cend(); }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800124
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800125 // Returns whether a mapping exists for the given key.
126 bool contains(const key_type& key) const {
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800127 return get(key, [](const mapped_type&) {});
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800128 }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800129
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800130 // 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 //
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800134 // const auto opt = map.get('c');
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800135 // assert(opt == 'C');
136 //
137 // char d = 'd';
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800138 // const auto ref = map.get('d').value_or(std::ref(d));
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800139 // ref.get() = 'D';
140 // assert(d == 'D');
141 //
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800142 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); });
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800144 }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800145
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800146 auto get(const key_type& key) -> std::optional<std::reference_wrapper<mapped_type>> {
147 return get(key, [](mapped_type& v) { return std::ref(v); });
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800148 }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800149
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800150 // 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 //
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800156 // assert(map.get('c', [](char c) { return std::toupper(c); }) == 'Z');
157 // assert(map.get('c', [](char& c) { c = std::toupper(c); }));
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800158 //
159 template <typename F, typename R = std::invoke_result_t<F, const mapped_type&>>
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800160 auto get(const key_type& key, F f) const
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800161 -> std::conditional_t<std::is_void_v<R>, bool, std::optional<R>> {
162 for (auto& [k, v] : *this) {
Dominik Laskowski18748962021-09-26 18:47:58 -0700163 if (KeyEqual{}(k, key)) {
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800164 if constexpr (std::is_void_v<R>) {
165 f(v);
166 return true;
167 } else {
168 return f(v);
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800169 }
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800170 }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800171 }
172
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800173 return {};
174 }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800175
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800176 template <typename F>
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800177 auto get(const key_type& key, F f) {
178 return std::as_const(*this).get(
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800179 key, [&f](const mapped_type& v) { return f(const_cast<mapped_type&>(v)); });
180 }
181
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800182 // Returns an iterator to an existing mapping for the given key, or the end() iterator otherwise.
183 const_iterator find(const key_type& key) const { return const_cast<SmallMap&>(*this).find(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>
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>
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>
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 //
245 bool erase(const key_type& key) { return erase(key, begin()); }
246
Dominik Laskowski44828ce2021-09-13 11:00:22 -0700247 // Removes all mappings.
248 //
249 // All iterators are invalidated.
250 //
251 void clear() { map_.clear(); }
252
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800253 private:
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800254 iterator find(const key_type& key, iterator first) {
Dominik Laskowski18748962021-09-26 18:47:58 -0700255 return std::find_if(first, end(),
256 [&key](const auto& pair) { return KeyEqual{}(pair.first, key); });
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800257 }
258
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
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
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800274 Map map_;
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800275};
276
277// Deduction guide for in-place constructor.
Dominik Laskowski18748962021-09-26 18:47:58 -0700278template <typename K, typename V, typename E, std::size_t... Sizes, typename... Types>
279SmallMap(InitializerList<KeyValue<K, V, E>, std::index_sequence<Sizes...>, Types...>&&)
280 -> SmallMap<K, V, sizeof...(Sizes), E>;
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800281
282// Returns whether the key-value pairs of two maps are equal.
Dominik Laskowski18748962021-09-26 18:47:58 -0700283template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M, typename E>
284bool operator==(const SmallMap<K, V, N, E>& lhs, const SmallMap<Q, W, M, E>& rhs) {
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800285 if (lhs.size() != rhs.size()) return false;
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800286
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800287 for (const auto& [k, v] : lhs) {
288 const auto& lv = v;
Dominik Laskowskidda9bba2021-02-03 18:56:00 -0800289 if (!rhs.get(k, [&lv](const auto& rv) { return lv == rv; }).value_or(false)) {
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800290 return false;
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800291 }
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800292 }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800293
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800294 return true;
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800295}
296
297// TODO: Remove in C++20.
Dominik Laskowski18748962021-09-26 18:47:58 -0700298template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M, typename E>
299inline bool operator!=(const SmallMap<K, V, N, E>& lhs, const SmallMap<Q, W, M, E>& rhs) {
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800300 return !(lhs == rhs);
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800301}
302
Dominik Laskowskie21dbed2020-12-04 20:51:43 -0800303} // namespace android::ftl