Commit 7eefd6cf authored by Jan Wilken Dörrie's avatar Jan Wilken Dörrie Committed by Commit Bot

[base] Add fixed_flat_map and fixed_flat_set

This change adds base::fixed_flat_map and base::fixed_flat_set as
specializations of base::flat_map and base::flat_set using a std::array
as the underlying container. These containers have immutable keys
following their construction, and can be used as constexpr lookup tables
in case the keys (and values) are known at compile time and of
appropriate types.

Bug: 682254
Change-Id: I47725824078beeca5100f502041c4977efde5868
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2532247Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Commit-Queue: Jan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#826866}
parent e87c7796
......@@ -179,6 +179,8 @@ component("base") {
"containers/checked_range.h",
"containers/circular_deque.h",
"containers/contiguous_iterator.h",
"containers/fixed_flat_map.h",
"containers/fixed_flat_set.h",
"containers/flat_map.h",
"containers/flat_set.h",
"containers/flat_tree.cc",
......@@ -2728,6 +2730,8 @@ test("base_unittests") {
"containers/checked_range_unittest.cc",
"containers/circular_deque_unittest.cc",
"containers/contiguous_iterator_unittest.cc",
"containers/fixed_flat_map_unittest.cc",
"containers/fixed_flat_set_unittest.cc",
"containers/flat_map_unittest.cc",
"containers/flat_set_unittest.cc",
"containers/flat_tree_unittest.cc",
......
......@@ -183,6 +183,28 @@ EXPECT_EQ(str_to_int.end(), str_to_int.find("c")->second);
str_to_int["c"] = 3;
```
### base::fixed\_flat\_map and base::fixed\_flat\_set
These are specializations of `base::flat_map` and `base::flat_set` that operate
on a sorted `std::array` instead of a sorted `std::vector`. These containers
have immutable keys, and don't support adding or removing elements once they are
constructed. However, these containers are constructed on the stack and don't
have any space overhead compared to a plain array. Furthermore, these containers
are constexpr friendly (assuming the key and mapped types are), and thus can be
used as compile time lookup tables.
To aid their constructions type deduction helpers in the form of
`base::MakeFixedFlatMap` and `base::MakeFixedFlatSet` are provided. These will
CHECK whether the provided elements are sorted and unique, failing compilation
if this precondition is violated in a constexpr context.
Example:
```cpp
constexpr auto kMap = base::MakeFixedFlatMap<base::StringPiece, int>(
{{"bar", 1}, {"baz", 2}, {"foo", 3}});
```
### base::small\_map
A small inline buffer that is brute-force searched that overflows into a full
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_CONTAINERS_FIXED_FLAT_MAP_H_
#define BASE_CONTAINERS_FIXED_FLAT_MAP_H_
#include <array>
#include <functional>
#include <utility>
#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_tree.h"
namespace base {
// fixed_flat_map is a immutable container with a std::map-like interface that
// stores its contents in a sorted std::array.
//
// It is a special case of base::flat_map, and mostly useful as a look-up table.
//
// Please see //base/containers/README.md for an overview of which container
// to select.
//
// QUICK REFERENCE
//
// Most of the core functionality is inherited from flat_tree. Please see
// flat_tree.h for more details for most of these functions. As a quick
// reference, the functions available are:
//
// Constructors (inputs need to be sorted):
// fixed_flat_map(const fixed_flat_map&);
// fixed_flat_map(sorted_unique_t,
// const container_type& items,
// const Compare& compare = Compare());
//
// Size management functions:
// size_t size() const;
// size_t max_size() const;
// bool empty() const;
//
// Iterator functions:
// iterator begin();
// const_iterator begin() const;
// const_iterator cbegin() const;
// iterator end();
// const_iterator end() const;
// const_iterator cend() const;
// reverse_iterator rbegin();
// const reverse_iterator rbegin() const;
// const_reverse_iterator crbegin() const;
// reverse_iterator rend();
// const_reverse_iterator rend() const;
// const_reverse_iterator crend() const;
//
// Insert and accessor functions:
// mapped_type& at(const K&);
// const mapped_type& at(const K&) const;
// Comparators (see std::map documentation).
// key_compare key_comp() const;
// value_compare value_comp() const;
//
// Search functions:
// template <typename K> size_t count(const K&) const;
// template <typename K> iterator find(const K&);
// template <typename K> const_iterator find(const K&) const;
// template <typename K> bool contains(const K&) const;
// template <typename K>
// pair<iterator, iterator> equal_range(const K&);
// template <typename K>
// pair<const_iterator, const_iterator> equal_range(K&) const;
// template <typename K> iterator lower_bound(const K&);
// template <typename K> const_iterator lower_bound(const K&) const;
// template <typename K> iterator upper_bound(const K&);
// template <typename K> const_iterator upper_bound(const K&) const;
//
// Non-member operators:
// bool operator==(const fixed_flat_map&, const fixed_flat_map&);
// bool operator!=(const fixed_flat_map&, const fixed_flat_map&);
// bool operator<(const fixed_flat_map&, const fixed_flat_map&);
// bool operator>(const fixed_flat_map&, const fixed_flat_map&);
// bool operator>=(const fixed_flat_map&, const fixed_flat_map&);
// bool operator<=(const fixed_flat_map&, const fixed_flat_map&);
//
template <class Key, class Mapped, size_t N, class Compare = std::less<>>
using fixed_flat_map = base::
flat_map<Key, Mapped, Compare, std::array<std::pair<const Key, Mapped>, N>>;
// Utility function to simplify constructing a fixed_flat_map from a fixed list
// of keys and values. Requires that the passed in `data` is sorted and unique.
//
// Example usages:
// constexpr auto kMap = base::MakeFixedFlatMap<base::StringPiece, int>(
// {{"bar", 1}, {"baz", 2}, {"foo", 3}});
template <class Key, class Mapped, size_t N, class Compare = std::less<>>
constexpr fixed_flat_map<Key, Mapped, N, Compare> MakeFixedFlatMap(
const std::pair<const Key, Mapped> (&data)[N],
const Compare& comp = Compare()) {
CHECK(internal::is_sorted_and_unique(data, comp));
return fixed_flat_map<Key, Mapped, N, Compare>(sorted_unique,
internal::ToArray(data), comp);
}
} // namespace base
#endif // BASE_CONTAINERS_FIXED_FLAT_MAP_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/containers/fixed_flat_map.h"
#include <string>
#include "base/ranges/algorithm.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::ElementsAre;
using ::testing::Pair;
namespace base {
TEST(FixedFlatMapTest, MakeFixedFlatMapTest) {
constexpr auto kSquares =
MakeFixedFlatMap<int, int>({{1, 1}, {2, 4}, {3, 9}, {4, 16}});
static_assert(ranges::is_sorted(kSquares), "Error: Map is not sorted.");
static_assert(ranges::adjacent_find(kSquares) == kSquares.end(),
"Error: Map contains repeated elements.");
EXPECT_THAT(kSquares,
ElementsAre(Pair(1, 1), Pair(2, 4), Pair(3, 9), Pair(4, 16)));
}
// Tests that even though the keys are immutable, the values of a non-const map
// can still be changed.
TEST(FixedFlatMapTest, MutableValues) {
auto map = MakeFixedFlatMap<std::string, int>({{"bar", 1}, {"foo", 2}});
EXPECT_THAT(map, ElementsAre(Pair("bar", 1), Pair("foo", 2)));
map.at("bar") = 2;
EXPECT_THAT(map, ElementsAre(Pair("bar", 2), Pair("foo", 2)));
}
} // namespace base
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_CONTAINERS_FIXED_FLAT_SET_H_
#define BASE_CONTAINERS_FIXED_FLAT_SET_H_
#include <array>
#include <functional>
#include "base/check.h"
#include "base/containers/flat_set.h"
#include "base/containers/flat_tree.h"
namespace base {
// fixed_flat_set is a immutable container with a std::set-like interface that
// stores its contents in a sorted std::array.
//
// It is a special case of base::flat_set, and mostly useful as a look-up table.
//
// Please see //base/containers/README.md for an overview of which container
// to select.
//
// QUICK REFERENCE
//
// Most of the core functionality is inherited from flat_tree. Please see
// flat_tree.h for more details for most of these functions. As a quick
// reference, the functions available are:
//
// Constructors (inputs need to be sorted):
// fixed_flat_set(const fixed_flat_set&);
// fixed_flat_set(sorted_unique_t,
// const container_type& items,
// const Compare& compare = Compare());
//
// Size management functions:
// size_t size() const;
// size_t max_size() const;
// bool empty() const;
//
// Iterator functions:
// const_iterator begin() const;
// const_iterator cbegin() const;
// const_iterator end() const;
// const_iterator cend() const;
// const reverse_iterator rbegin() const;
// const_reverse_iterator crbegin() const;
// const_reverse_iterator rend() const;
// const_reverse_iterator crend() const;
//
// Comparators (see std::set documentation).
// key_compare key_comp() const;
// value_compare value_comp() const;
//
// Search functions:
// template <typename K> size_t count(const K&) const;
// template <typename K> const_iterator find(const K&) const;
// template <typename K> bool contains(const K&) const;
// template <typename K>
// pair<const_iterator, const_iterator> equal_range(K&) const;
// template <typename K> const_iterator lower_bound(const K&) const;
// template <typename K> const_iterator upper_bound(const K&) const;
//
// Non-member operators:
// bool operator==(const fixed_flat_set&, const fixed_flat_set&);
// bool operator!=(const fixed_flat_set&, const fixed_flat_set&);
// bool operator<(const fixed_flat_set&, const fixed_flat_set&);
// bool operator>(const fixed_flat_set&, const fixed_flat_set&);
// bool operator>=(const fixed_flat_set&, const fixed_flat_set&);
// bool operator<=(const fixed_flat_set&, const fixed_flat_set&);
//
template <class Key, size_t N, class Compare = std::less<>>
using fixed_flat_set = base::flat_set<Key, Compare, std::array<const Key, N>>;
// Utility function to simplify constructing a fixed_flat_set from a fixed list
// of keys. Requires that the passed in `data` is sorted and unique.
//
// Example usage:
// constexpr auto kSet = base::MakeFixedFlatSet({1, 2, 3, 4});
template <class Key, size_t N, class Compare = std::less<>>
constexpr fixed_flat_set<Key, N, Compare> MakeFixedFlatSet(
const Key (&data)[N],
const Compare& comp = Compare()) {
CHECK(internal::is_sorted_and_unique(data, comp));
// Specify the value_type explicitly to ensure that the returned array has
// immutable keys.
return fixed_flat_set<Key, N, Compare>(
sorted_unique, internal::ToArray<const Key>(data), comp);
}
} // namespace base
#endif // BASE_CONTAINERS_FIXED_FLAT_SET_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/containers/fixed_flat_set.h"
#include "base/ranges/algorithm.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
TEST(FixedFlatSetTest, MakeFixedFlatSet) {
constexpr auto kSet = MakeFixedFlatSet({1, 2, 3, 4});
static_assert(ranges::is_sorted(kSet), "Error: Set is not sorted.");
static_assert(ranges::adjacent_find(kSet) == kSet.end(),
"Error: Set contains repeated elements.");
EXPECT_THAT(kSet, ::testing::ElementsAre(1, 2, 3, 4));
}
} // namespace base
......@@ -20,8 +20,8 @@ namespace internal {
// An implementation of the flat_tree GetKeyFromValue template parameter that
// extracts the key as the first element of a pair.
template <class Key, class Mapped>
struct GetKeyFromValuePairFirst {
struct GetFirst {
template <class Key, class Mapped>
const Key& operator()(const std::pair<Key, Mapped>& p) const {
return p.first;
}
......@@ -176,17 +176,11 @@ template <class Key,
class Mapped,
class Compare = std::less<>,
class Container = std::vector<std::pair<Key, Mapped>>>
class flat_map : public ::base::internal::flat_tree<
Key,
::base::internal::GetKeyFromValuePairFirst<Key, Mapped>,
Compare,
Container> {
class flat_map : public ::base::internal::
flat_tree<Key, internal::GetFirst, Compare, Container> {
private:
using tree = typename ::base::internal::flat_tree<
Key,
::base::internal::GetKeyFromValuePairFirst<Key, Mapped>,
Compare,
Container>;
using tree = typename ::base::internal::
flat_tree<Key, internal::GetFirst, Compare, Container>;
public:
using key_type = typename tree::key_type;
......
......@@ -31,7 +31,7 @@ namespace internal {
// Helper functions used in DCHECKs below to make sure that inputs tagged with
// sorted_unique are indeed sorted and unique.
template <typename Range, typename Comp>
bool is_sorted_and_unique(const Range& range, Comp comp) {
constexpr bool is_sorted_and_unique(const Range& range, Comp comp) {
return ranges::is_sorted(range, comp) &&
// Being unique implies that there are no adjacent elements that
// compare equal.
......@@ -54,6 +54,24 @@ template <typename T>
struct IsTransparentCompare<T, void_t<typename T::is_transparent>>
: std::true_type {};
// Helper inspired by C++20's std::to_array to convert a C-style array to a
// std::array. As opposed to the C++20 version this implementation does not
// provide an overload for rvalues and does not strip cv qualifers from the
// returned std::array::value_type, allowing the construction of std::arrays
// with const elements.
//
// Reference: https://en.cppreference.com/w/cpp/container/array/to_array
template <class T, size_t N, size_t... I>
constexpr std::array<T, N> ToArrayImpl(const T (&data)[N],
std::index_sequence<I...>) {
return {{data[I]...}};
}
template <typename T, size_t N>
constexpr std::array<T, N> ToArray(const T (&data)[N]) {
return ToArrayImpl<T, N>(data, std::make_index_sequence<N>());
}
// Implementation -------------------------------------------------------------
// Implementation for the sorted associative flat_set and flat_map using a
......@@ -83,7 +101,7 @@ class flat_tree {
value_compare() = default;
template <class Cmp>
explicit value_compare(Cmp&& compare_arg)
constexpr explicit value_compare(Cmp&& compare_arg)
: KeyCompare(std::forward<Cmp>(compare_arg)) {}
bool operator()(const value_type& left, const value_type& right) const {
......@@ -149,9 +167,9 @@ class flat_tree {
const container_type& items,
const key_compare& comp = key_compare());
flat_tree(sorted_unique_t,
container_type&& items,
const key_compare& comp = key_compare());
constexpr flat_tree(sorted_unique_t,
container_type&& items,
const key_compare& comp = key_compare());
flat_tree(sorted_unique_t,
std::initializer_list<value_type> ilist,
......@@ -201,11 +219,11 @@ class flat_tree {
// construction of the flat_tree.
iterator begin();
const_iterator begin() const;
constexpr const_iterator begin() const;
const_iterator cbegin() const;
iterator end();
const_iterator end() const;
constexpr const_iterator end() const;
const_iterator cend() const;
reverse_iterator rbegin();
......@@ -268,6 +286,9 @@ class flat_tree {
// idiom when deleting multiple non-consecutive elements.
iterator erase(iterator position);
// Artificially templatized to break ambiguity if `iterator` and
// `const_iterator` are the same type.
template <typename DummyT = void>
iterator erase(const_iterator position);
iterator erase(const_iterator first, const_iterator last);
template <typename K>
......@@ -486,7 +507,7 @@ class flat_tree {
Impl() = default;
template <class Cmp, class... Body>
explicit Impl(Cmp&& compare_arg, Body&&... underlying_type_args)
constexpr explicit Impl(Cmp&& compare_arg, Body&&... underlying_type_args)
: value_compare(std::forward<Cmp>(compare_arg)),
body_(std::forward<Body>(underlying_type_args)...) {}
......@@ -563,7 +584,7 @@ flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::flat_tree(
}
template <class Key, class GetKeyFromValue, class KeyCompare, class Container>
flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::flat_tree(
constexpr flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::flat_tree(
sorted_unique_t,
container_type&& items,
const KeyCompare& comp)
......@@ -654,8 +675,8 @@ auto flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::begin()
}
template <class Key, class GetKeyFromValue, class KeyCompare, class Container>
auto flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::begin() const
-> const_iterator {
constexpr auto flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::begin()
const -> const_iterator {
return ranges::begin(impl_.body_);
}
......@@ -671,8 +692,8 @@ auto flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::end() -> iterator {
}
template <class Key, class GetKeyFromValue, class KeyCompare, class Container>
auto flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::end() const
-> const_iterator {
constexpr auto flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::end()
const -> const_iterator {
return ranges::end(impl_.body_);
}
......@@ -837,6 +858,7 @@ auto flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::erase(
}
template <class Key, class GetKeyFromValue, class KeyCompare, class Container>
template <typename DummyT>
auto flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::erase(
const_iterator position) -> iterator {
CHECK(position != impl_.body_.end());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment