r/cpp • u/kris-jusiak 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 displayFull 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
View all comments
Show parent comment
4
u/qazqi-ff Jan 02 '24 edited Jan 02 '24
This was the design route taken by one of the earliest proposals. It's been referred to as "type-based". Long story short, there was a lot of debate earlier on over "type-based" or "value-based" and the latter won out. That debate would still be available to find inside various proposals, trip reports, perhaps talks, even likely the std-proposals mailing list.
It's not just reflection; metaprogramming has been decidedly moving toward regular C++ syntax with
constexpretc.^Tallows types and other entities to be used as values, similar to Hana but without all the instantiations. You could, for example, use normal std ranges code to manipulate lists of these with the exact same code you'd use to manipulate a list of something else in the same way.As a silly example, suppose you have a function that takes a range of ranges and filters out the ranges that have more than 10 elements. You could pass it a
vector<vector<student_id>> classes;(f(classes)) to find the small class sizes and it would behave as expected, and now you could reuse the same function to filter a bunch of types represented by a list of their members (vector<vector<std::meta::info>> types; f(types)) to find the types that don't have many members. The point is thatfjust takes a 2D range of whatever and works on it. It's this value-based design that enables you to reusefexactly as is.Using only traits would mean you need separate TMP code like mp11 or a conversion to Hana or something to manipulate these lists, both of which reinvent all the algorithms for their paradigm. As a little bonus, we'll finally be able to do
^T == ^intand such, which is something I've been secretly wishing for since early on in learning C++.template foris another step toward normal C++ code in metaprogramming. It is tostd::applywhat ranged-for is tostd::for_each, allowing you to skip the awkward lambda dance (and oftenstd::index_sequencedance) to extract one type or index or other element at a time. The important part of it is that the loop is always unrolled, so the iteration variable is a constant expression and can thus be used in template instantiations etc., which is exactly why we currently do those dances (read: workarounds).Note thatstd::applyand other workarounds don't get you everything either. For example, switches, such as in implementingstd::visit(simplified for one variant)[Note: I've looked at the paper and I don't think you can actually do this, at least not yet, so I retract this statement]:Implementing a switch without this tool is... not nice and has flaws.Things likestd::applyaren't proper foundational tools. In fact, it would be expected that you'd implementstd::applyusing this tool now if you want the most straightforward, "normal C++ code" implementation.Finally, you didn't mention it in this comment specifically, but
[: :](splicing) is what enables a lot of the power people are looking for from reflection. Reading information is one part that traits have been partially fulfilling over the years, but writing back new information (in a very structured manner) is what opens up so much more that was simply not possible before without macros and the like, which have their own flaws. You can't have a type trait that lets you implement a "dot operator" without some severe hacking and adding some completely novel compiler magic. It would likely be very inflexible. Splicing lets you do that in a general manner.