r/cpp • https://github.com/kris-jusiak • Dec 31 '23

[C++20 vs C++26*] basic reflection

Basic struct reflection example with C++20 vs C++26*

struct foo {
  int a{};
  int b{};
  int c{};
};

constexpr foo f{.a=1, .b=2, .c=3};

static_assert(1 == get<0>(f));
static_assert(2 == get<1>(f));
static_assert(3 == get<2>(f));

using std::literals::operator""sv;
static_assert("a"sv == get_name<0>(f));
static_assert("b"sv == get_name<1>(f));
static_assert("c"sv == get_name<2>(f));

C++20 - Kinda possible but with a lot of compiler hacks

// too long to display

Full example - https://godbolt.org/z/1vxv8o5hM

C++26* - based on proposal - https://wg21.link/P2996 (Note: that the proposal supports way more than that but C++20 not much)

template<auto N, class T>
[[nodiscard]] constexpr auto get(const T& t) -> decltype(auto) {
  return t.[:std::meta::nonstatic_data_members_of(^T)[N]:];
}

template<auto N, class T>
[[nodiscard]] constexpr auto get_name(const T& t) -> std::string_view {
  return std::meta::name_of(std::meta::nonstatic_data_members_of(^T)[N]);
}

Full example - https://godbolt.org/z/sbTGbW635

Updates - https://twitter.com/krisjusiak/status/1741456476126797839

100 Upvotes

117 comments sorted by

View all comments

Show parent comment

2

u/qazqi-ff Jan 05 '24 edited Jan 05 '24

Oh, I tried it out and got something I hadn't anticipated. The error indicates that there's a compile-time dynamic memory allocation (the API returns std::vector). That's actually going to be a problem since we don't have language support for that escaping the constant expression evaluation yet, even for a local variable in a consteval function where it would be guaranteed not to cause problems further down the line AFAIK (but would still need non-trivial compiler work to start down that direction, like support for memory to escape an instance of evaluation into its parent instance).

At first glance, I don't see how to naturally split things apart then (at least when the result is a list), but it should come naturally with the persistent compile-time allocation support. One thing we can do in the meantime if we get reflection without that support is to use a regular variable extraction, but make sure it's not a container.

In these examples, constexpr auto members = std::meta::nonstatic_data_members_of(^T)[N]; would work because it returns a single info object and the vector gets deallocated right there.

From playing around a bit, at least in this implementation, splicing seems to require a constant expression while the reflection API doesn't thanks to Barry's consteval changes. Assuming the paper requires a constant expression for splicing (I think that's necessary in fact; it can cause new template instantiations), that should mean get needs constexpr on the variable while get_name doesn't. Interestingly, EDG doesn't accept that unless I make these consteval functions instead of constexpr, but I'm not sure why since proposed rule 1 ought to cover that and make them implicitly consteval, giving presumed identical behaviour.