blob: bd35c4963a86ea5d3d67a879137237afcd54abb4 [file] [log] [blame]
// Copyright 2021 gRPC authors.
//
// 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.
#ifndef GRPC_SRC_CORE_UTIL_TABLE_H
#define GRPC_SRC_CORE_UTIL_TABLE_H
#include <grpc/support/port_platform.h>
#include <stddef.h>
#include <initializer_list>
#include <new>
#include <type_traits>
#include <utility>
#include "absl/meta/type_traits.h"
#include "absl/utility/utility.h"
#include "src/core/util/bitset.h"
namespace grpc_core {
// Meta-programming detail types to aid in building up a Table
namespace table_detail {
// A tuple-like type that contains manually constructed elements.
template <typename, typename... Ts>
struct Elements;
template <typename T, typename... Ts>
struct Elements<absl::enable_if_t<!std::is_empty<T>::value>, T, Ts...>
: Elements<void, Ts...> {
struct alignas(T) Data {
unsigned char bytes[sizeof(T)];
};
Data x;
Elements() {}
T* ptr() { return reinterpret_cast<T*>(x.bytes); }
const T* ptr() const { return reinterpret_cast<const T*>(x.bytes); }
};
template <typename T, typename... Ts>
struct Elements<absl::enable_if_t<std::is_empty<T>::value>, T, Ts...>
: Elements<void, Ts...> {
T* ptr() { return reinterpret_cast<T*>(this); }
const T* ptr() const { return reinterpret_cast<const T*>(this); }
};
template <>
struct Elements<void> {};
// Element accessor for Elements<>
// Provides a static method f that returns a pointer to the value of element I
// for Elements<Ts...>
template <size_t I, typename... Ts>
struct GetElem;
template <typename T, typename... Ts>
struct GetElem<0, T, Ts...> {
static T* f(Elements<void, T, Ts...>* e) { return e->ptr(); }
static const T* f(const Elements<void, T, Ts...>* e) { return e->ptr(); }
};
template <size_t I, typename T, typename... Ts>
struct GetElem<I, T, Ts...> {
static auto f(Elements<void, T, Ts...>* e)
-> decltype(GetElem<I - 1, Ts...>::f(e)) {
return GetElem<I - 1, Ts...>::f(e);
}
static auto f(const Elements<void, T, Ts...>* e)
-> decltype(GetElem<I - 1, Ts...>::f(e)) {
return GetElem<I - 1, Ts...>::f(e);
}
};
// CountIncludedStruct is the backing for the CountIncluded function below.
// Sets a member constant N to the number of times Needle is in Haystack.
template <typename Needle, typename... Haystack>
struct CountIncludedStruct;
template <typename Needle, typename Straw, typename... RestOfHaystack>
struct CountIncludedStruct<Needle, Straw, RestOfHaystack...> {
static constexpr size_t N =
static_cast<size_t>(std::is_same<Needle, Straw>::value) +
CountIncludedStruct<Needle, RestOfHaystack...>::N;
};
template <typename Needle>
struct CountIncludedStruct<Needle> {
static constexpr size_t N = 0;
};
// Returns the number of times Needle is in Haystack.
template <typename Needle, typename... Haystack>
constexpr size_t CountIncluded() {
return CountIncludedStruct<Needle, Haystack...>::N;
}
// IndexOfStruct is the backing for IndexOf below.
// Set a member constant N to the index of Needle in Haystack.
// Ignored should be void always, and is used for enable_if_t.
template <typename Ignored, typename Needle, typename... Haystack>
struct IndexOfStruct;
template <typename Needle, typename Straw, typename... RestOfHaystack>
struct IndexOfStruct<absl::enable_if_t<std::is_same<Needle, Straw>::value>,
Needle, Straw, RestOfHaystack...> {
// The first element is the one we're looking for. Done.
static constexpr size_t N = 0;
};
template <typename Needle, typename Straw, typename... RestOfHaystack>
struct IndexOfStruct<absl::enable_if_t<!std::is_same<Needle, Straw>::value>,
Needle, Straw, RestOfHaystack...> {
// The first element is not the one we're looking for, recurse looking at the
// tail, and sum the number of recursions.
static constexpr size_t N =
1 + IndexOfStruct<void, Needle, RestOfHaystack...>::N;
};
// Return the index of Needle in Haystack.
// Guarded by CountIncluded to ensure that the return type is unambiguous.
// If you got here from a compiler error using Table, it's likely that you've
// used the type-based accessor/mutators, but the type you're using is repeated
// more than once in the Table type arguments. Consider either using the indexed
// accessor/mutator variants, or eliminating the ambiguity in type resolution.
template <typename Needle, typename... Haystack>
constexpr absl::enable_if_t<CountIncluded<Needle, Haystack...>() == 1, size_t>
IndexOf() {
return IndexOfStruct<void, Needle, Haystack...>::N;
}
// TypeIndexStruct is the backing for TypeIndex below.
// Sets member type Type to the type at index I in Ts.
// Implemented as a simple type recursion.
template <size_t I, typename... Ts>
struct TypeIndexStruct;
template <typename T, typename... Ts>
struct TypeIndexStruct<0, T, Ts...> {
using Type = T;
};
template <size_t I, typename T, typename... Ts>
struct TypeIndexStruct<I, T, Ts...> : TypeIndexStruct<I - 1, Ts...> {};
// TypeIndex is the type at index I in Ts.
template <size_t I, typename... Ts>
using TypeIndex = typename TypeIndexStruct<I, Ts...>::Type;
// Helper to call the destructor of p if p is non-null.
template <typename T>
void DestructIfNotNull(T* p) {
if (p) p->~T();
}
// Helper function... just ignore the initializer list passed into it.
// Allows doing 'statements' via parameter pack expansion in C++11 - given
// template <typename... Ts>:
// do_these_things({(foo<Ts>(), 1)});
// will execute foo<T>() for each T in Ts.
// In this example we also leverage the comma operator to make the resultant
// type of each statement be a consistant int so that C++ type deduction works
// as we'd like (note that in the expression (a, 1) in C++, the 'result' of the
// expression is the value after the right-most ',' -- in this case 1, with a
// executed as a side effect.
template <typename T>
void do_these_things(std::initializer_list<T>) {}
} // namespace table_detail
// A Table<Ts> is much like a tuple<optional<Ts>...> - a set of values that are
// optionally present. Table efficiently packs the presence bits for size, and
// provides a slightly more convenient interface.
template <typename... Ts>
class Table {
// Helper - TypeIndex<I> is the type at index I in Ts
template <size_t I>
using TypeIndex = table_detail::TypeIndex<I, Ts...>;
public:
// Construct a table with no values set.
Table() = default;
// Destruct - forwards to the Destruct member with an integer sequence so we
// can destruct field-wise.
~Table() { Destruct(absl::make_index_sequence<sizeof...(Ts)>()); }
// Copy another table
Table(const Table& rhs) {
// Since we know all fields are clear initially, pass false for or_clear.
Copy<false>(absl::make_index_sequence<sizeof...(Ts)>(), rhs);
}
// Copy another table
Table& operator=(const Table& rhs) {
// Since we may not be all clear, pass true for or_clear to have Copy()
// clear newly emptied fields.
Copy<true>(absl::make_index_sequence<sizeof...(Ts)>(), rhs);
return *this;
}
// Move from another table
Table(Table&& rhs) noexcept {
// Since we know all fields are clear initially, pass false for or_clear.
Move<false>(absl::make_index_sequence<sizeof...(Ts)>(),
std::forward<Table>(rhs));
}
// Move from another table
Table& operator=(Table&& rhs) noexcept {
// Since we may not be all clear, pass true for or_clear to have Move()
// clear newly emptied fields.
Move<true>(absl::make_index_sequence<sizeof...(Ts)>(),
std::forward<Table>(rhs));
return *this;
}
// Check if this table has a value for type T.
// Only available if there exists only one T in Ts.
template <typename T>
bool has() const {
return has<index_of<T>()>();
}
// Check if this table has index I.
template <size_t I>
absl::enable_if_t<(I < sizeof...(Ts)), bool> has() const {
return present_bits_.is_set(I);
}
// Return the value for type T, or nullptr if it is un-set.
// Only available if there exists only one T in Ts.
template <typename T>
T* get() {
return get<index_of<T>()>();
}
// Return the value for type T, or nullptr if it is un-set.
// Only available if there exists only one T in Ts.
template <typename T>
const T* get() const {
return get<index_of<T>()>();
}
// Return the value for index I, or nullptr if it is un-set.
template <size_t I>
TypeIndex<I>* get() {
if (has<I>()) return element_ptr<I>();
return nullptr;
}
// Return the value for index I, or nullptr if it is un-set.
template <size_t I>
const TypeIndex<I>* get() const {
if (has<I>()) return element_ptr<I>();
return nullptr;
}
// Return the value for type T, default constructing it if it is un-set.
template <typename T>
T* get_or_create() {
return get_or_create<index_of<T>()>();
}
// Return the value for index I, default constructing it if it is un-set.
template <size_t I>
TypeIndex<I>* get_or_create() {
auto* p = element_ptr<I>();
if (!set_present<I>(true)) {
new (p) TypeIndex<I>();
}
return element_ptr<I>();
}
// Set the value for type T - using Args as construction arguments.
template <typename T, typename... Args>
T* set(Args&&... args) {
return set<index_of<T>()>(std::forward<Args>(args)...);
}
// Set the value for index I - using Args as construction arguments.
template <size_t I, typename... Args>
TypeIndex<I>* set(Args&&... args) {
auto* p = element_ptr<I>();
if (set_present<I>(true)) {
TypeIndex<I> replacement(std::forward<Args>(args)...);
*p = std::move(replacement);
} else {
new (p) TypeIndex<I>(std::forward<Args>(args)...);
}
return p;
}
template <size_t I>
TypeIndex<I>* set(TypeIndex<I>&& value) {
auto* p = element_ptr<I>();
if (set_present<I>(true)) {
*p = std::forward<TypeIndex<I>>(value);
} else {
new (p) TypeIndex<I>(std::forward<TypeIndex<I>>(value));
}
return p;
}
// Clear the value for type T, leaving it un-set.
template <typename T>
void clear() {
clear<index_of<T>()>();
}
// Clear the value for index I, leaving it un-set.
template <size_t I>
void clear() {
if (set_present<I>(false)) {
using T = TypeIndex<I>;
element_ptr<I>()->~T();
}
}
// Iterate through each set field in the table
template <typename F>
void ForEach(F f) const {
ForEachImpl(std::move(f), absl::make_index_sequence<sizeof...(Ts)>());
}
// Iterate through each set field in the table if it exists in Vs, in the
// order of Vs.
template <typename F, typename... Vs>
void ForEachIn(F f) const {
ForEachImpl(std::move(f),
absl::index_sequence<table_detail::IndexOf<Vs, Ts...>()...>());
}
// Iterate through each set field in the table if it exists in Vs, in the
// order of Vs. For each existing field, call the filter function. If the
// function returns true, keep the field. Otherwise, remove the field.
template <typename F, typename... Vs>
void FilterIn(F f) {
FilterInImpl(std::move(f),
absl::index_sequence<table_detail::IndexOf<Vs, Ts...>()...>());
}
// Count the number of set fields in the table
size_t count() const { return present_bits_.count(); }
// Check if the table is completely empty
bool empty() const { return present_bits_.none(); }
// Clear all elements in the table.
void ClearAll() { ClearAllImpl(absl::make_index_sequence<sizeof...(Ts)>()); }
private:
// Bit field for which elements of the table are set (true) or un-set (false,
// the default) -- one bit for each type in Ts.
using PresentBits = BitSet<sizeof...(Ts)>;
// The tuple-like backing structure for Table.
using Elements = table_detail::Elements<void, Ts...>;
// Extractor from Elements
template <size_t I>
using GetElem = table_detail::GetElem<I, Ts...>;
// Given a T, return the unambiguous index of it within Ts.
template <typename T>
static constexpr size_t index_of() {
return table_detail::IndexOf<T, Ts...>();
}
// Given an index, return a point to the (maybe uninitialized!) data value at
// index I.
template <size_t I>
TypeIndex<I>* element_ptr() {
return GetElem<I>::f(&elements_);
}
// Given an index, return a point to the (maybe uninitialized!) data value at
// index I.
template <size_t I>
const TypeIndex<I>* element_ptr() const {
return GetElem<I>::f(&elements_);
}
// Set the present bit to value (if true - value is present/set, if false,
// value is un-set). Returns the old value so that calling code can note
// transition edges.
template <size_t I>
bool set_present(bool value) {
bool out = present_bits_.is_set(I);
present_bits_.set(I, value);
return out;
}
// Set the value of index I to the value held in rhs index I if it is set.
// If it is unset, if or_clear is true, then clear our value, otherwise do
// nothing.
template <bool or_clear, size_t I>
void CopyIf(const Table& rhs) {
if (auto* p = rhs.get<I>()) {
set<I>(*p);
} else if (or_clear) {
clear<I>();
}
}
// Set the value of index I to the value moved from rhs index I if it was set.
// If it is unset, if or_clear is true, then clear our value, otherwise do
// nothing.
template <bool or_clear, size_t I>
void MoveIf(Table&& rhs) {
if (auto* p = rhs.get<I>()) {
set<I>(std::move(*p));
} else if (or_clear) {
clear<I>();
}
}
// Call (*f)(value) if that value is in the table.
template <size_t I, typename F>
void CallIf(F* f) const {
if (auto* p = get<I>()) {
(*f)(*p);
}
}
// Call (*f)(value) if that value is in the table.
// If the value is present in the table and (*f)(value) returns false, remove
// the value from the table.
template <size_t I, typename F>
void FilterIf(F* f) {
if (auto* p = get<I>()) {
if (!(*f)(*p)) {
clear<I>();
}
}
}
// For each field (element I=0, 1, ...) if that field is present, call its
// destructor.
template <size_t... I>
void Destruct(absl::index_sequence<I...>) {
table_detail::do_these_things<int>(
{(table_detail::DestructIfNotNull(get<I>()), 1)...});
}
// For each field (element I=0, 1, ...) copy that field into this table -
// or_clear as per CopyIf().
template <bool or_clear, size_t... I>
void Copy(absl::index_sequence<I...>, const Table& rhs) {
table_detail::do_these_things<int>({(CopyIf<or_clear, I>(rhs), 1)...});
}
// For each field (element I=0, 1, ...) move that field into this table -
// or_clear as per MoveIf().
template <bool or_clear, size_t... I>
void Move(absl::index_sequence<I...>, Table&& rhs) {
table_detail::do_these_things<int>(
{(MoveIf<or_clear, I>(std::forward<Table>(rhs)), 1)...});
}
// For each field (element I=0, 1, ...) if that field is present, call f.
template <typename F, size_t... I>
void ForEachImpl(F f, absl::index_sequence<I...>) const {
table_detail::do_these_things<int>({(CallIf<I>(&f), 1)...});
}
// For each field (element I=0, 1, ...) if that field is present, call f. If
// f returns false, remove the field from the table.
template <typename F, size_t... I>
void FilterInImpl(F f, absl::index_sequence<I...>) {
table_detail::do_these_things<int>({(FilterIf<I>(&f), 1)...});
}
template <size_t... I>
void ClearAllImpl(absl::index_sequence<I...>) {
table_detail::do_these_things<int>({(clear<I>(), 1)...});
}
// Bit field indicating which elements are set.
PresentBits present_bits_;
// The memory to store the elements themselves.
Elements elements_;
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_UTIL_TABLE_H