Printing class data members using C++26 reflection
C++26’s reflection feature allows programs to analyse themselves at compile time (the proposal paper can be read here). This post illustrates a function to print a struct’s data members. You can run it here on Compiler Explorer using Bloomberg’s reflection-enabled Clang fork (or view it here on GitHub).
Prerequisites
- Compiler:
x86-64 clang (reflection - C++26)on Compiler Explorer - Flags:
-freflection-latest - Includes:
<meta>,<print>and<type_traits>
Implementation
The struct whose members we’ll print:
struct Foo {
int a;
float b;
char c;
};
To shorten the code, I alias the reflection std::meta namespace to m using namespace m = std::meta;.
This is the full function, followed by a line by line explanation:
template <typename T>
requires std::is_class_v<T>
void print_data_members() {
constexpr auto ctx{m::access_context::unchecked()};
constexpr auto members{std::define_static_array(m::members_of(^^T, ctx))};
std::print("Type: {}\n", m::identifier_of(^^T));
template for (constexpr m::info member : members) {
if constexpr (m::is_nonstatic_data_member(member)) {
constexpr auto type{m::type_of(member)};
std::print("{} : {}\n",
m::identifier_of(member),
m::display_string_of(type)
);
}
}
}
int main() {
print_data_members<Foo>();
return 0;
}
print_data_members uses std::is_class_v from <type_traits> to reject non-class types.
template <typename T>
requires std::is_class_v<T>
To access all the class members we will use std::meta::members_of.
Its parameters are an std::meta::info object and an access context specifier.
The info object can be created using the reflection “cat-ears” operator ^^ on our struct type.
access_context is used to control the level of access when reflecting on a type e.g. can you see private member variables or not?
We need to feed the members_of output to std::define_static_array as the std::vector<info> returned by members_of cannot be used in a constexpr context.
This is explained in the “Expansion Statements” paper.
constexpr auto ctx{m::access_context::unchecked()};
constexpr auto members{std::define_static_array(m::members_of(^^T, ctx))};
I get and print the struct’s name using std::meta::identifier_of and std::print respectively.
std::print("Type: {}\n", m::identifier_of(^^T));
The new template for does a compile-time loop over our members array.
template for (constexpr m::info member : members) {}
I filter out non-data members using if constexpr (m::is_nonstatic_data_member(member)).
I get the member’s type with std::meta::type_of.
std::meta::identifier_of throws compiler errors for primitive types like int so we use std::meta::display_string_of instead.
constexpr auto type{m::type_of(member)};
std::print("{} : {}\n", m::identifier_of(member), m::display_string_of(type));
In our main function, we call print_data_members like any other function template.
print_data_members<Foo>();
The program’s output is:
Type: Foo
a : int
b : float
c : char
References: