// 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 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 5 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 #include namespace Carbon::StructReflection { namespace Internal { // A type that can be converted to any field type within type T. template struct AnyField { template // NOLINTNEXTLINE(google-explicit-constructor) operator FieldT&() const; template // NOLINTNEXTLINE(google-explicit-constructor) 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 constexpr auto CanListInitialize(decltype(T{Fields()...})* /*unused*/) -> bool { return true; } template constexpr auto CanListInitialize(...) -> bool { return false; } #pragma clang diagnostic pop // Simple detector to find the number of data fields in a struct. This proceeds // in two passes: // // 1) Add AnyFields until we can initialize T from our list of initializers. // 2) Add more AnyFields until we can't initialize any more. template constexpr auto CountFields() -> int { if constexpr (CanListInitialize(0)) { return CountFields>(); } else if constexpr (AnyWorkedSoFar) { static_assert(sizeof...(Fields) <= 5, "Unsupported: too many fields in struct"); return sizeof...(Fields) - 1; } 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>(); } } // Utility to access fields by index. template struct FieldAccessor; template <> struct FieldAccessor<0> { template static auto Get(T& /*value*/) -> auto { return std::tuple<>(); } }; template <> struct FieldAccessor<1> { template static auto Get(T& value) -> auto { auto& [field0] = value; return std::tuple(field0); } }; template <> struct FieldAccessor<2> { template static auto Get(T& value) -> auto { auto& [field0, field1] = value; return std::tuple(field0, field1); } }; template <> struct FieldAccessor<3> { template static auto Get(T& value) -> auto { auto& [field0, field1, field2] = value; return std::tuple( field0, field1, field2); } }; template <> struct FieldAccessor<4> { template static auto Get(T& value) -> auto { auto& [field0, field1, field2, field3] = value; return std::tuple(field0, field1, field2, field3); } }; template <> struct FieldAccessor<5> { template static auto Get(T& value) -> auto { auto& [field0, field1, field2, field3, field4] = value; return std::tuple( field0, field1, field2, field3, field4); } }; } // namespace Internal // Get the fields of the struct `T` as a tuple. template auto AsTuple(T value) -> auto { // We use aggregate initialization to detect the number of fields. static_assert(std::is_aggregate_v, "Only aggregates are supported"); return Internal::FieldAccessor()>::Get(value); } } // namespace Carbon::StructReflection #endif // CARBON_COMMON_STRUCT_REFLECTION_H_