diff options
| -rw-r--r-- | include/ftl/InitializerList.h | 41 | ||||
| -rw-r--r-- | include/ftl/SmallMap.h | 205 | ||||
| -rw-r--r-- | libs/ftl/Android.bp | 1 | ||||
| -rw-r--r-- | libs/ftl/SmallMap_test.cpp | 131 |
4 files changed, 378 insertions, 0 deletions
diff --git a/include/ftl/InitializerList.h b/include/ftl/InitializerList.h index 204f79bb92..bb99280745 100644 --- a/include/ftl/InitializerList.h +++ b/include/ftl/InitializerList.h @@ -34,6 +34,14 @@ namespace android::ftl { // // ... = ftl::init::list<std::string>("abc")()(3u, '?'); // +// The following syntax is a shorthand for key-value pairs, where the first argument is the +// key, and the rest construct the value. The types of the key and value are deduced if the +// first pair contains exactly two arguments: +// +// ... = ftl::init::map<int, std::string>(-1, "abc")(-2)(-3, 3u, '?'); +// +// ... = ftl::init::map(0, 'a')(1, 'b')(2, 'c'); +// // WARNING: The InitializerList returned by an ftl::init::list expression must be consumed // immediately, since temporary arguments are destroyed after the full expression. Storing // an InitializerList results in dangling references. @@ -58,6 +66,29 @@ struct InitializerList<T, std::index_sequence<Sizes...>, Types...> { std::tuple<Types...> tuple; }; +template <typename K, typename V> +struct KeyValue {}; + +// Shorthand for key-value pairs that assigns the first argument to the key, and the rest to the +// value. The specialization is on KeyValue rather than std::pair, so that ftl::init::list works +// with the latter. +template <typename K, typename V, size_t... Sizes, typename... Types> +struct InitializerList<KeyValue<K, V>, std::index_sequence<Sizes...>, Types...> { + // Accumulate the three arguments to std::pair's piecewise constructor. + template <typename... Args> + [[nodiscard]] constexpr auto operator()(K&& k, Args&&... args) && -> InitializerList< + KeyValue<K, V>, std::index_sequence<Sizes..., 3>, Types..., std::piecewise_construct_t, + std::tuple<K&&>, std::tuple<Args&&...>> { + return {std::tuple_cat(std::move(tuple), + std::forward_as_tuple(std::piecewise_construct, + std::forward_as_tuple(std::forward<K>(k)), + std::forward_as_tuple( + std::forward<Args>(args)...)))}; + } + + std::tuple<Types...> tuple; +}; + namespace init { template <typename T, typename... Args> @@ -65,5 +96,15 @@ template <typename T, typename... Args> return InitializerList<T>{}(std::forward<Args>(args)...); } +template <typename K, typename V, typename... Args> +[[nodiscard]] constexpr auto map(Args&&... args) { + return list<KeyValue<K, V>>(std::forward<Args>(args)...); +} + +template <typename K, typename V> +[[nodiscard]] constexpr auto map(K&& k, V&& v) { + return list<KeyValue<K, V>>(std::forward<K>(k), std::forward<V>(v)); +} + } // namespace init } // namespace android::ftl diff --git a/include/ftl/SmallMap.h b/include/ftl/SmallMap.h new file mode 100644 index 0000000000..87ae99cace --- /dev/null +++ b/include/ftl/SmallMap.h @@ -0,0 +1,205 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <ftl/InitializerList.h> +#include <ftl/SmallVector.h> + +#include <functional> +#include <optional> +#include <type_traits> +#include <utility> + +namespace android::ftl { + +// Associative container with unique, unordered keys. Unlike std::unordered_map, key-value pairs are +// stored in contiguous storage for cache efficiency. The map is allocated statically until its size +// exceeds N, at which point mappings are relocated to dynamic memory. +// +// SmallMap<K, V, 0> unconditionally allocates on the heap. +// +// Example usage: +// +// ftl::SmallMap<int, std::string, 3> map; +// assert(map.empty()); +// assert(!map.dynamic()); +// +// map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?'); +// assert(map.size() == 3u); +// assert(!map.dynamic()); +// +// assert(map.contains(123)); +// assert(map.find(42, [](const std::string& s) { return s.size(); }) == 3u); +// +// const auto opt = map.find(-1); +// assert(opt); +// +// std::string& ref = *opt; +// assert(ref.empty()); +// ref = "xyz"; +// +// assert(map == SmallMap(ftl::init::map(-1, "xyz")(42, "???")(123, "abc"))); +// +template <typename K, typename V, size_t N> +class SmallMap final { + using Map = SmallVector<std::pair<const K, V>, N>; + +public: + using key_type = K; + using mapped_type = V; + + using value_type = typename Map::value_type; + using size_type = typename Map::size_type; + using difference_type = typename Map::difference_type; + + using reference = typename Map::reference; + using iterator = typename Map::iterator; + + using const_reference = typename Map::const_reference; + using const_iterator = typename Map::const_iterator; + + // Creates an empty map. + SmallMap() = default; + + // Constructs at most N key-value pairs in place by forwarding per-pair constructor arguments. + // The template arguments K, V, and N are inferred using the deduction guide defined below. + // The syntax for listing pairs is as follows: + // + // ftl::SmallMap map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?'); + // + // static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, std::string, 3>>); + // assert(map.size() == 3u); + // assert(map.contains(-1) && map.find(-1)->get().empty()); + // assert(map.contains(42) && map.find(42)->get() == "???"); + // assert(map.contains(123) && map.find(123)->get() == "abc"); + // + // The types of the key and value are deduced if the first pair contains exactly two arguments: + // + // ftl::SmallMap map = ftl::init::map(0, 'a')(1, 'b')(2, 'c'); + // static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, char, 3>>); + // + template <typename U, size_t... Sizes, typename... Types> + SmallMap(InitializerList<U, std::index_sequence<Sizes...>, Types...>&& init) + : mMap(std::move(init)) { + // TODO: Enforce unique keys. + } + + size_type max_size() const { return mMap.max_size(); } + size_type size() const { return mMap.size(); } + bool empty() const { return mMap.empty(); } + + // Returns whether the map is backed by static or dynamic storage. + bool dynamic() const { return mMap.dynamic(); } + + iterator begin() { return mMap.begin(); } + const_iterator begin() const { return cbegin(); } + const_iterator cbegin() const { return mMap.cbegin(); } + + iterator end() { return mMap.end(); } + const_iterator end() const { return cend(); } + const_iterator cend() const { return mMap.cend(); } + + // Returns whether a mapping exists for the given key. + bool contains(const key_type& key) const { + return find(key, [](const mapped_type&) {}); + } + + // Returns a reference to the value for the given key, or std::nullopt if the key was not found. + // + // ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C'); + // + // const auto opt = map.find('c'); + // assert(opt == 'C'); + // + // char d = 'd'; + // const auto ref = map.find('d').value_or(std::ref(d)); + // ref.get() = 'D'; + // assert(d == 'D'); + // + auto find(const key_type& key) const + -> std::optional<std::reference_wrapper<const mapped_type>> { + return find(key, [](const mapped_type& v) { return std::cref(v); }); + } + + auto find(const key_type& key) -> std::optional<std::reference_wrapper<mapped_type>> { + return find(key, [](mapped_type& v) { return std::ref(v); }); + } + + // Returns the result R of a unary operation F on (a constant or mutable reference to) the value + // for the given key, or std::nullopt if the key was not found. If F has a return type of void, + // then the Boolean result indicates whether the key was found. + // + // ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); + // + // assert(map.find('c', [](char c) { return std::toupper(c); }) == 'Z'); + // assert(map.find('c', [](char& c) { c = std::toupper(c); })); + // + template <typename F, typename R = std::invoke_result_t<F, const mapped_type&>> + auto find(const key_type& key, F f) const + -> std::conditional_t<std::is_void_v<R>, bool, std::optional<R>> { + for (auto& [k, v] : *this) { + if (k == key) { + if constexpr (std::is_void_v<R>) { + f(v); + return true; + } else { + return f(v); + } + } + } + + return {}; + } + + template <typename F> + auto find(const key_type& key, F f) { + return std::as_const(*this).find(key, [&f](const mapped_type& v) { + return f(const_cast<mapped_type&>(v)); + }); + } + +private: + Map mMap; +}; + +// Deduction guide for in-place constructor. +template <typename K, typename V, size_t... Sizes, typename... Types> +SmallMap(InitializerList<KeyValue<K, V>, std::index_sequence<Sizes...>, Types...>&&) + -> SmallMap<K, V, sizeof...(Sizes)>; + +// Returns whether the key-value pairs of two maps are equal. +template <typename K, typename V, size_t N, typename Q, typename W, size_t M> +bool operator==(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) { + if (lhs.size() != rhs.size()) return false; + + for (const auto& [k, v] : lhs) { + const auto& lv = v; + if (!rhs.find(k, [&lv](const auto& rv) { return lv == rv; }).value_or(false)) { + return false; + } + } + + return true; +} + +// TODO: Remove in C++20. +template <typename K, typename V, size_t N, typename Q, typename W, size_t M> +inline bool operator!=(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) { + return !(lhs == rhs); +} + +} // namespace android::ftl diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index fb3238215c..eb8e57a911 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -5,6 +5,7 @@ cc_test { address: true, }, srcs: [ + "SmallMap_test.cpp", "SmallVector_test.cpp", "StaticVector_test.cpp", ], diff --git a/libs/ftl/SmallMap_test.cpp b/libs/ftl/SmallMap_test.cpp new file mode 100644 index 0000000000..fa00c06a70 --- /dev/null +++ b/libs/ftl/SmallMap_test.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ftl/SmallMap.h> +#include <gtest/gtest.h> + +#include <cctype> + +namespace android::test { + +using ftl::SmallMap; + +// Keep in sync with example usage in header file. +TEST(SmallMap, Example) { + ftl::SmallMap<int, std::string, 3> map; + EXPECT_TRUE(map.empty()); + EXPECT_FALSE(map.dynamic()); + + map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?'); + EXPECT_EQ(map.size(), 3u); + EXPECT_FALSE(map.dynamic()); + + EXPECT_TRUE(map.contains(123)); + + EXPECT_EQ(map.find(42, [](const std::string& s) { return s.size(); }), 3u); + + const auto opt = map.find(-1); + ASSERT_TRUE(opt); + + std::string& ref = *opt; + EXPECT_TRUE(ref.empty()); + ref = "xyz"; + + EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "xyz")(42, "???")(123, "abc"))); +} + +TEST(SmallMap, Construct) { + { + // Default constructor. + SmallMap<int, std::string, 2> map; + + EXPECT_TRUE(map.empty()); + EXPECT_FALSE(map.dynamic()); + } + { + // In-place constructor with same types. + SmallMap<int, std::string, 5> map = + ftl::init::map<int, std::string>(123, "abc")(456, "def")(789, "ghi"); + + EXPECT_EQ(map.size(), 3u); + EXPECT_EQ(map.max_size(), 5u); + EXPECT_FALSE(map.dynamic()); + + EXPECT_EQ(map, SmallMap(ftl::init::map(123, "abc")(456, "def")(789, "ghi"))); + } + { + // In-place constructor with different types. + SmallMap<int, std::string, 5> map = + ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?'); + + EXPECT_EQ(map.size(), 3u); + EXPECT_EQ(map.max_size(), 5u); + EXPECT_FALSE(map.dynamic()); + + EXPECT_EQ(map, SmallMap(ftl::init::map(42, "???")(123, "abc")(-1, "\0\0\0"))); + } + { + // In-place constructor with implicit size. + SmallMap map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?'); + + static_assert(std::is_same_v<decltype(map), SmallMap<int, std::string, 3>>); + EXPECT_EQ(map.size(), 3u); + EXPECT_EQ(map.max_size(), 3u); + EXPECT_FALSE(map.dynamic()); + + EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "\0\0\0")(42, "???")(123, "abc"))); + } +} + +TEST(SmallMap, Find) { + { + // Constant reference. + const ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C'); + + const auto opt = map.find('b'); + EXPECT_EQ(opt, 'B'); + + const char d = 'D'; + const auto ref = map.find('d').value_or(std::cref(d)); + EXPECT_EQ(ref.get(), 'D'); + } + { + // Mutable reference. + ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C'); + + const auto opt = map.find('c'); + EXPECT_EQ(opt, 'C'); + + char d = 'd'; + const auto ref = map.find('d').value_or(std::ref(d)); + ref.get() = 'D'; + EXPECT_EQ(d, 'D'); + } + { + // Constant unary operation. + const ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); + EXPECT_EQ(map.find('c', [](char c) { return std::toupper(c); }), 'Z'); + } + { + // Mutable unary operation. + ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z'); + EXPECT_TRUE(map.find('c', [](char& c) { c = std::toupper(c); })); + + EXPECT_EQ(map, SmallMap(ftl::init::map('c', 'Z')('b', 'y')('a', 'x'))); + } +} + +} // namespace android::test |