blob: 246792c8869869193b27fa06aadad5eddba0d546 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_CONTAINERS_VARIANT_MAP_H_
#define BASE_CONTAINERS_VARIANT_MAP_H_
#include <map>
#include <type_traits>
#include "base/check_op.h"
#include "base/features.h"
#include "base/gtest_prod_util.h"
#include "base/notreached.h"
#include "base/types/pass_key.h"
#include "build/build_config.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
namespace metrics {
class SubprocessMetricsProvider;
}
namespace mojo {
template <typename ContextType>
class BinderMapWithContext;
class ReceiverSetState;
template <typename ReceiverType, typename ContextType>
class ReceiverSetBase;
}
namespace resource_attribution {
class CPUMeasurementMonitor;
class FakeMemoryMeasurementDelegateFactory;
class MemoryMeasurementDelegate;
class QueryResultMap;
} // namespace resource_attribution
namespace base {
// Enum to specify which underlying map implementation to use.
enum class MapType {
// See StdMapVariant
kStdMap,
// See FlatHashMapVariant
kFlatHashMap,
};
// Whether the AbslFlatMapInVariantMap feature is enabled.
BASE_EXPORT bool IsAbslFlatMapInVariantMapEnabled();
// Initializes VariantMap features. See `base::features::Init()`.
BASE_EXPORT void InitializeVariantMapFeatures();
// Class used to evaluate the performance of switching from std::map to
// absl::flat_hash_map in place. This class is used exactly like the underlying
// map implementation for the implemented subset of operations. Constructing can
// be done with a chosen `MapType` or if the default constructor is used the
// variant is chosen automatically with a base::Feature.
//
// Example:
// VariantMap<int, int> map(MapType::kFlatHashMap);
// map[4] = 5;
//
// Since this class supports backing map implementations with different
// guarantees users have to assume that the least permissive guarantees apply.
// This includes but is not limited to:
// 1) No specific entry ordering
// 2) No iterator stability through modifications
// 3) No storage stability through modifications
//
// TODO(crbug.com/433462519): Remove this entire class by M145.
template <typename Key, typename Value>
class VariantMap {
public:
using StdMapVariant = std::map<Key, Value>;
using FlatHashMapVariant = absl::flat_hash_map<Key, Value>;
using value_type = std::pair<const Key, Value>;
// Iterator class used to erase the difference in backend variant. This should
// mostly be a drop-in replacement for the iterator types of the underlying
// map and user code that already uses `auto` for iterator variables should
// not need to be updated.
template <bool is_const>
class IteratorImpl {
public:
using StdMapIter =
std::conditional_t<is_const,
typename StdMapVariant::const_iterator,
typename StdMapVariant::iterator>;
using FlatHashMapIter =
std::conditional_t<is_const,
typename FlatHashMapVariant::const_iterator,
typename FlatHashMapVariant::iterator>;
using pointer =
std::conditional_t<is_const, const value_type*, value_type*>;
using reference =
std::conditional_t<is_const, const value_type&, value_type&>;
explicit IteratorImpl(StdMapIter it) : iter_variant_(it) {}
explicit IteratorImpl(FlatHashMapIter it) : iter_variant_(it) {}
// Allow narrowing access from a non-const iterator to a const iterator but
// not the opposite.
template <bool other_is_const>
requires(is_const && !other_is_const)
explicit IteratorImpl(IteratorImpl<other_is_const> other)
: iter_variant_(std::visit(
[](const auto& it) { return decltype(iter_variant_)(it); },
other.iter_variant_)) {}
IteratorImpl& operator++() {
std::visit([](auto& it) { ++it; }, iter_variant_);
return *this;
}
reference operator*() const {
return std::visit([](auto& it) -> reference { return *it; },
iter_variant_);
}
pointer operator->() const {
// Tie the implementation of this operator to operator*.
// *this accesses this instance * of that uses the * operator.
// & of that result is a pointer to the value.
return &(**this);
}
bool operator==(const IteratorImpl& other) const {
// Comparing iterators from different variants should never happen.
CHECK_EQ(iter_variant_.index(), other.iter_variant_.index());
return iter_variant_ == other.iter_variant_;
}
private:
template <bool>
friend class IteratorImpl;
// For access to `iter_variant_`.
friend class VariantMap;
// The variant holds the specific iterator from the underlying map.
std::variant<StdMapIter, FlatHashMapIter> iter_variant_;
};
using iterator = IteratorImpl<false>;
using const_iterator = IteratorImpl<true>;
// Protected by PassKey because not intended for general use but only
// experimenting.
template <typename ContextType>
explicit VariantMap(
base::PassKey<mojo::BinderMapWithContext<ContextType>> passkey)
: VariantMap() {}
template <typename ReceiverType, typename ContextType>
explicit VariantMap(
base::PassKey<mojo::ReceiverSetBase<ReceiverType, ContextType>> passkey)
: VariantMap() {}
explicit VariantMap(base::PassKey<metrics::SubprocessMetricsProvider> passkey)
: VariantMap() {}
explicit VariantMap(base::PassKey<mojo::ReceiverSetState> passkey)
: VariantMap() {}
explicit VariantMap(
base::PassKey<resource_attribution::CPUMeasurementMonitor> passkey)
: VariantMap() {}
explicit VariantMap(
base::PassKey<resource_attribution::FakeMemoryMeasurementDelegateFactory>
passkey)
: VariantMap() {}
explicit VariantMap(
base::PassKey<resource_attribution::MemoryMeasurementDelegate> passkey)
: VariantMap() {}
explicit VariantMap(
base::PassKey<resource_attribution::QueryResultMap> passkey)
: VariantMap() {}
size_t size() const {
return std::visit([](const auto& map) { return map.size(); }, data_);
}
bool empty() const {
return std::visit([](const auto& map) { return map.empty(); }, data_);
}
void clear() {
return std::visit([](auto& map) { return map.clear(); }, data_);
}
Value& operator[](const Key& key) {
return std::visit([&key](auto& map) -> Value& { return map[key]; }, data_);
}
Value& at(const Key& key) {
return std::visit([&key](auto& map) -> Value& { return map.at(key); },
data_);
}
const Value& at(const Key& key) const {
return std::visit(
[&key](const auto& map) -> const Value& { return map.at(key); }, data_);
}
template <class... Args>
std::pair<iterator, bool> emplace(Args&&... args) {
return std::visit(
[&](auto& map) {
auto result = map.emplace(std::forward<Args>(args)...);
return std::make_pair(iterator(result.first), result.second);
},
data_);
}
template <class... Args>
std::pair<iterator, bool> try_emplace(Args&&... args) {
return std::visit(
[&](auto& map) {
auto result = map.try_emplace(std::forward<Args>(args)...);
return std::make_pair(iterator(result.first), result.second);
},
data_);
}
std::pair<iterator, bool> insert(value_type&& value) {
return std::visit(
[&](auto& map) {
auto result = map.insert(std::move(value));
return std::make_pair(iterator(result.first), result.second);
},
data_);
}
size_t erase(const Key& key) {
return std::visit([&](auto& map) { return map.erase(key); }, data_);
}
void erase(iterator pos) {
std::visit(
[&](auto& map) {
// Get the correct native_iterator out.
using MapType = typename std::decay<decltype(map)>::type;
auto native_it =
std::get<typename MapType::iterator>(pos.iter_variant_);
map.erase(native_it);
},
data_);
}
iterator begin() {
return std::visit([](auto& map) { return iterator(map.begin()); }, data_);
}
const_iterator begin() const {
return const_iterator(const_cast<VariantMap*>(this)->begin());
}
iterator end() {
return std::visit([](auto& map) { return iterator(map.end()); }, data_);
}
const_iterator end() const {
return const_iterator(const_cast<VariantMap*>(this)->end());
}
iterator find(const Key& key) {
return std::visit([&key](auto& map) { return iterator(map.find(key)); },
data_);
}
const_iterator find(const Key& key) const {
return const_iterator(const_cast<VariantMap*>(this)->find(key));
}
private:
FRIEND_TEST_ALL_PREFIXES(VariantMapTest, Construction);
FRIEND_TEST_ALL_PREFIXES(VariantMapTest, Insertion);
FRIEND_TEST_ALL_PREFIXES(VariantMapTest, At);
FRIEND_TEST_ALL_PREFIXES(VariantMapTest, Find);
FRIEND_TEST_ALL_PREFIXES(VariantMapTest, Iteration);
FRIEND_TEST_ALL_PREFIXES(VariantMapTest, Empty);
FRIEND_TEST_ALL_PREFIXES(VariantMapTest, Clear);
using Map = std::variant<StdMapVariant, FlatHashMapVariant>;
// Constructors private because protected via PassKey.
explicit VariantMap(MapType type)
: data_([&]() {
switch (type) {
case MapType::kStdMap:
return Map(std::in_place_index_t<0>());
case MapType::kFlatHashMap:
return Map(std::in_place_index_t<1>());
NOTREACHED();
}
}()) {}
VariantMap()
: VariantMap(base::IsAbslFlatMapInVariantMapEnabled()
? MapType::kFlatHashMap
: MapType::kStdMap) {}
// The variant that holds one of the two map types.
Map data_;
};
} // namespace base
#endif // BASE_CONTAINERS_VARIANT_MAP_H_