blob: d05836950a6bfbac0cd8f3b282c8d25bf69dc554 [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
22#include <functional>
23#include <optional>
24#include <type_traits>
25#include <utility>
26
27namespace android::ftl {
28
29// Associative container with unique, unordered keys. Unlike std::unordered_map, key-value pairs are
30// stored in contiguous storage for cache efficiency. The map is allocated statically until its size
31// exceeds N, at which point mappings are relocated to dynamic memory.
32//
33// SmallMap<K, V, 0> unconditionally allocates on the heap.
34//
35// Example usage:
36//
37// ftl::SmallMap<int, std::string, 3> map;
38// assert(map.empty());
39// assert(!map.dynamic());
40//
41// map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
42// assert(map.size() == 3u);
43// assert(!map.dynamic());
44//
45// assert(map.contains(123));
46// assert(map.find(42, [](const std::string& s) { return s.size(); }) == 3u);
47//
48// const auto opt = map.find(-1);
49// assert(opt);
50//
51// std::string& ref = *opt;
52// assert(ref.empty());
53// ref = "xyz";
54//
55// assert(map == SmallMap(ftl::init::map(-1, "xyz")(42, "???")(123, "abc")));
56//
Dominik Laskowski5444fc82020-11-24 13:41:10 -080057template <typename K, typename V, std::size_t N>
Dominik Laskowskic4b91462020-11-02 13:37:25 -080058class SmallMap final {
59 using Map = SmallVector<std::pair<const K, V>, N>;
60
61public:
62 using key_type = K;
63 using mapped_type = V;
64
65 using value_type = typename Map::value_type;
66 using size_type = typename Map::size_type;
67 using difference_type = typename Map::difference_type;
68
69 using reference = typename Map::reference;
70 using iterator = typename Map::iterator;
71
72 using const_reference = typename Map::const_reference;
73 using const_iterator = typename Map::const_iterator;
74
75 // Creates an empty map.
76 SmallMap() = default;
77
78 // Constructs at most N key-value pairs in place by forwarding per-pair constructor arguments.
79 // The template arguments K, V, and N are inferred using the deduction guide defined below.
80 // The syntax for listing pairs is as follows:
81 //
82 // ftl::SmallMap map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
83 //
84 // static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, std::string, 3>>);
85 // assert(map.size() == 3u);
86 // assert(map.contains(-1) && map.find(-1)->get().empty());
87 // assert(map.contains(42) && map.find(42)->get() == "???");
88 // assert(map.contains(123) && map.find(123)->get() == "abc");
89 //
90 // The types of the key and value are deduced if the first pair contains exactly two arguments:
91 //
92 // ftl::SmallMap map = ftl::init::map(0, 'a')(1, 'b')(2, 'c');
93 // static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, char, 3>>);
94 //
Dominik Laskowski5444fc82020-11-24 13:41:10 -080095 template <typename U, std::size_t... Sizes, typename... Types>
96 SmallMap(InitializerList<U, std::index_sequence<Sizes...>, Types...>&& list)
97 : map_(std::move(list)) {
Dominik Laskowskic4b91462020-11-02 13:37:25 -080098 // TODO: Enforce unique keys.
99 }
100
Dominik Laskowski5444fc82020-11-24 13:41:10 -0800101 size_type max_size() const { return map_.max_size(); }
102 size_type size() const { return map_.size(); }
103 bool empty() const { return map_.empty(); }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800104
105 // Returns whether the map is backed by static or dynamic storage.
Dominik Laskowski5444fc82020-11-24 13:41:10 -0800106 bool dynamic() const { return map_.dynamic(); }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800107
Dominik Laskowski5444fc82020-11-24 13:41:10 -0800108 iterator begin() { return map_.begin(); }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800109 const_iterator begin() const { return cbegin(); }
Dominik Laskowski5444fc82020-11-24 13:41:10 -0800110 const_iterator cbegin() const { return map_.cbegin(); }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800111
Dominik Laskowski5444fc82020-11-24 13:41:10 -0800112 iterator end() { return map_.end(); }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800113 const_iterator end() const { return cend(); }
Dominik Laskowski5444fc82020-11-24 13:41:10 -0800114 const_iterator cend() const { return map_.cend(); }
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800115
116 // Returns whether a mapping exists for the given key.
117 bool contains(const key_type& key) const {
118 return find(key, [](const mapped_type&) {});
119 }
120
121 // Returns a reference to the value for the given key, or std::nullopt if the key was not found.
122 //
123 // ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
124 //
125 // const auto opt = map.find('c');
126 // assert(opt == 'C');
127 //
128 // char d = 'd';
129 // const auto ref = map.find('d').value_or(std::ref(d));
130 // ref.get() = 'D';
131 // assert(d == 'D');
132 //
133 auto find(const key_type& key) const
134 -> std::optional<std::reference_wrapper<const mapped_type>> {
135 return find(key, [](const mapped_type& v) { return std::cref(v); });
136 }
137
138 auto find(const key_type& key) -> std::optional<std::reference_wrapper<mapped_type>> {
139 return find(key, [](mapped_type& v) { return std::ref(v); });
140 }
141
142 // Returns the result R of a unary operation F on (a constant or mutable reference to) the value
143 // for the given key, or std::nullopt if the key was not found. If F has a return type of void,
144 // then the Boolean result indicates whether the key was found.
145 //
146 // ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
147 //
148 // assert(map.find('c', [](char c) { return std::toupper(c); }) == 'Z');
149 // assert(map.find('c', [](char& c) { c = std::toupper(c); }));
150 //
151 template <typename F, typename R = std::invoke_result_t<F, const mapped_type&>>
152 auto find(const key_type& key, F f) const
153 -> std::conditional_t<std::is_void_v<R>, bool, std::optional<R>> {
154 for (auto& [k, v] : *this) {
155 if (k == key) {
156 if constexpr (std::is_void_v<R>) {
157 f(v);
158 return true;
159 } else {
160 return f(v);
161 }
162 }
163 }
164
165 return {};
166 }
167
168 template <typename F>
169 auto find(const key_type& key, F f) {
170 return std::as_const(*this).find(key, [&f](const mapped_type& v) {
171 return f(const_cast<mapped_type&>(v));
172 });
173 }
174
175private:
Dominik Laskowski5444fc82020-11-24 13:41:10 -0800176 Map map_;
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800177};
178
179// Deduction guide for in-place constructor.
Dominik Laskowski5444fc82020-11-24 13:41:10 -0800180template <typename K, typename V, std::size_t... Sizes, typename... Types>
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800181SmallMap(InitializerList<KeyValue<K, V>, std::index_sequence<Sizes...>, Types...>&&)
182 -> SmallMap<K, V, sizeof...(Sizes)>;
183
184// Returns whether the key-value pairs of two maps are equal.
Dominik Laskowski5444fc82020-11-24 13:41:10 -0800185template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M>
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800186bool operator==(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) {
187 if (lhs.size() != rhs.size()) return false;
188
189 for (const auto& [k, v] : lhs) {
190 const auto& lv = v;
191 if (!rhs.find(k, [&lv](const auto& rv) { return lv == rv; }).value_or(false)) {
192 return false;
193 }
194 }
195
196 return true;
197}
198
199// TODO: Remove in C++20.
Dominik Laskowski5444fc82020-11-24 13:41:10 -0800200template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M>
Dominik Laskowskic4b91462020-11-02 13:37:25 -0800201inline bool operator!=(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) {
202 return !(lhs == rhs);
203}
204
205} // namespace android::ftl