mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:50:14 +01:00
clang-tidy 24 warns on C-style variadics, and we don't need to use one here anymore. Instead of a function call with an argument list, use a concept to determine if a type can be list initialized.
196 lines
6.1 KiB
C++
196 lines
6.1 KiB
C++
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
// Exceptions. See /LICENSE for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#ifndef CARBON_COMMON_STRUCT_REFLECTION_H_
|
|
#define CARBON_COMMON_STRUCT_REFLECTION_H_
|
|
|
|
// Reflection support for simple struct types.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// ```
|
|
// struct A { int x; std::string y; };
|
|
//
|
|
// A a;
|
|
// std::tuple<int, std::string> t = StructReflection::AsTuple(a);
|
|
// ```
|
|
//
|
|
// Limitations:
|
|
//
|
|
// - Only simple aggregate structs are supported. Types with base classes,
|
|
// non-public data members, constructors, or virtual functions are not
|
|
// supported.
|
|
// - Structs with more than 8 fields are not supported. This limit is easy to
|
|
// increase if needed, but removing it entirely is hard.
|
|
// - Structs containing a reference to the same type are not supported.
|
|
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
|
|
namespace Carbon::StructReflection {
|
|
|
|
namespace Internal {
|
|
|
|
// A type that can be converted to any field type within type T.
|
|
template <typename T>
|
|
struct AnyField {
|
|
template <typename FieldT>
|
|
// NOLINTNEXTLINE(google-explicit-constructor)
|
|
explicit(false) operator FieldT&() const;
|
|
|
|
template <typename FieldT>
|
|
// NOLINTNEXTLINE(google-explicit-constructor)
|
|
explicit(false) operator FieldT&&() const;
|
|
|
|
// Don't allow conversion to T itself. This ensures we don't match against a
|
|
// copy or move constructor.
|
|
operator T&() const = delete;
|
|
operator T&&() const = delete;
|
|
};
|
|
|
|
// The detection mechanism below intentionally misses field initializers.
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
|
|
|
|
// Detector for whether we can list-initialize T from the given list of fields.
|
|
template <typename T, typename... Fields>
|
|
concept CanListInitialize = requires { T{Fields()...}; };
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
// Simple detector to find the number of data fields in a struct. This proceeds
|
|
// in two passes:
|
|
//
|
|
// 1) Add AnyField<T>s until we can initialize T from our list of initializers.
|
|
// 2) Add more AnyField<T>s until we can't initialize any more.
|
|
template <typename T, bool AnyWorkedSoFar = false, typename... Fields>
|
|
constexpr auto CountFields() -> int {
|
|
if constexpr (CanListInitialize<T, Fields...>) {
|
|
return CountFields<T, true, Fields..., AnyField<T>>();
|
|
} else if constexpr (AnyWorkedSoFar) {
|
|
constexpr int NumFields = sizeof...(Fields) - 1;
|
|
static_assert(NumFields <= 8, "Unsupported: too many fields in struct");
|
|
return NumFields;
|
|
} else if constexpr (sizeof...(Fields) > 32) {
|
|
// If we go too far without finding a working initializer, something
|
|
// probably went wrong with our calculation. Bail out before we recurse too
|
|
// deeply.
|
|
static_assert(sizeof...(Fields) <= 32,
|
|
"Internal error, could not count fields in struct");
|
|
} else {
|
|
return CountFields<T, false, Fields..., AnyField<T>>();
|
|
}
|
|
}
|
|
|
|
// Utility to access fields by index.
|
|
template <int NumFields>
|
|
struct FieldAccessor;
|
|
|
|
template <>
|
|
struct FieldAccessor<0> {
|
|
template <typename T>
|
|
static auto Get(T& /*value*/) -> auto {
|
|
return std::tuple<>();
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<1> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0] = value;
|
|
return std::tuple<decltype(field0)>(field0);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<2> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1] = value;
|
|
return std::tuple<decltype(field0), decltype(field1)>(field0, field1);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<3> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1, field2] = value;
|
|
return std::tuple<decltype(field0), decltype(field1), decltype(field2)>(
|
|
field0, field1, field2);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<4> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1, field2, field3] = value;
|
|
return std::tuple<decltype(field0), decltype(field1), decltype(field2),
|
|
decltype(field3)>(field0, field1, field2, field3);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<5> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1, field2, field3, field4] = value;
|
|
return std::tuple<decltype(field0), decltype(field1), decltype(field2),
|
|
decltype(field3), decltype(field4)>(
|
|
field0, field1, field2, field3, field4);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<6> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1, field2, field3, field4, field5] = value;
|
|
return std::tuple<decltype(field0), decltype(field1), decltype(field2),
|
|
decltype(field3), decltype(field4), decltype(field5)>(
|
|
field0, field1, field2, field3, field4, field5);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<7> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1, field2, field3, field4, field5, field6] = value;
|
|
return std::tuple<decltype(field0), decltype(field1), decltype(field2),
|
|
decltype(field3), decltype(field4), decltype(field5),
|
|
decltype(field6)>(field0, field1, field2, field3, field4,
|
|
field5, field6);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<8> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1, field2, field3, field4, field5, field6, field7] =
|
|
value;
|
|
return std::tuple<decltype(field0), decltype(field1), decltype(field2),
|
|
decltype(field3), decltype(field4), decltype(field5),
|
|
decltype(field6), decltype(field7)>(
|
|
field0, field1, field2, field3, field4, field5, field6, field7);
|
|
}
|
|
};
|
|
|
|
} // namespace Internal
|
|
|
|
// Get the fields of the struct `T` as a tuple.
|
|
template <typename T>
|
|
auto AsTuple(T value) -> auto {
|
|
// We use aggregate initialization to detect the number of fields.
|
|
static_assert(std::is_aggregate_v<T>, "Only aggregates are supported");
|
|
return Internal::FieldAccessor<Internal::CountFields<T>()>::Get(value);
|
|
}
|
|
|
|
} // namespace Carbon::StructReflection
|
|
|
|
#endif // CARBON_COMMON_STRUCT_REFLECTION_H_
|