I'm exploring the upcoming C++26 reflections feature and I'm excited about its potential to simplify many tasks that involve introspection and metaprogramming. However, I'm not entirely sure how to use reflections properly and how to leverage them for various use cases effectively.
Could someone provide examples of how to use C++26 reflections correctly? Specifically, I'm interested in:
Any code examples and explanations would be highly appreciated!
Examples of Structs:
struct Person {
std::string name;
int age;
double height;
};
struct Address {
std::string street;
int number;
};
Desired Outputs:
Some of the API, particularly around recovering strings that represent names, is still in flux. The latest published revision contains an example of how to build a universal formatter, although we're thinking about introducing an API that is spelled identifier_of(mem)
instead.
So the intended implementation right now (assuming we get expansion statements) would look like this:
struct universal_formatter {
constexpr auto parse(auto& ctx) { return ctx.begin(); }
template <typename T>
auto format(T const& t, auto& ctx) const {
auto out = std::format_to(ctx.out(), "{}{{", identifier_of(^T));
auto delim = [first=true]() mutable {
if (!first) {
*out++ = ',';
*out++ = ' ';
}
first = false;
};
template for (constexpr auto base : bases_of(^T)) {
delim();
out = std::format_to(out, "{}", (typename [: type_of(base) :] const&)(t));
}
template for (constexpr auto mem : nonstatic_data_members_of(^T)) {
delim();
out = std::format_to(out, ".{}=", identifier_of(mem));
// this whole dance because we don't have :?
ctx.advance_to(out);
std::formatter<typename [:type_of(mem):]> f;
std::format_parse_context pctx("");
(void)f.parse(pctx);
if constexpr (requires { f.set_debug_format(); }) {
f.set_debug_format();
}
out = f.format(t.[:mem:], ctx);
}
*out++ = '}';
return out;
}
};
With which, you could:
struct Person {
std::string name;
int age;
double height;
};
// explicit opt-in, but note that you don't
// have to do anything other than this
template <> struct std::formatter<Person> : universal_formatter { };
int main() {
// prints: Person{.name="Caeleb Dressel", .age=27, .height=1.9}
std::println("{}", Person{.name="Caeleb Dressel", .age=27, .height=1.90});
}
Since we don't have expansion statements, there's an awkward workaround. But it is still doable: demo.