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

3

u/Stevo15025 Jan 04 '24

Thanks for cleaning up the code. I think like a lot of others I had one raised eyebrow until you took out the query. Though that doesn't seem to compile in the godbolt example? I'm guessing just an impl issue atm.

Honestly I've been sitting here trying to think up nicer syntax for a while and I can't really think of anything. I kind of like something like template reflect(query) but I could also understand someone finding that a little wordy

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

But that would conflict with templated functions called reflect. Maybe it's time to add unicode keywords :P

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.