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