Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c4b9146f authored by Dominik Laskowski's avatar Dominik Laskowski
Browse files

FTL: Add SmallMap<K, V, N>

SmallMap is a SmallVector of unordered key-value pairs. This CL includes
construction and lookup, while mutators will be added in a follow-up CL.

Bug: 160012986
Test: ftl_test
Change-Id: Id88485304c7d7f515226b4f8edf63ce9af6a2157
parent ccd50a4f
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
@@ -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

include/ftl/SmallMap.h

0 → 100644
+205 −0
Original line number Diff line number Diff line
/*
 * 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
+1 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ cc_test {
        address: true,
    },
    srcs: [
        "SmallMap_test.cpp",
        "SmallVector_test.cpp",
        "StaticVector_test.cpp",
    ],
+131 −0
Original line number Diff line number Diff line
/*
 * 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