Generating structs at compile time with C++26 reflection
Last week we looked at template for and how it simplifies working with tuples. This week, we're going a step further: creating entirely new types at compile time. You will find the link to the Godbolt example in the comment below.
The C++26 reflection functions you will learn about in this article are:
A new concept is a consteval block which is executed at compile time.
There is a limitation: currently, you can only add members to a struct, not functions.
Getting started
It is important for this function to work that the struct is a forward declaration only. If you give the struct a body, it is considered a complete type and define_aggregate will no longer work:
#include <meta>
#include <iostream>
#include <vector>
struct S_Particle;
The meta include file is needed for std::meta::info, data_member_spec and define aggregate. I named the struct S_Particle, where the 'S' stands for synthetic.
Consteval block
The consteval block runs at compile time, defines a vector with some members and adds the desired members to the incomplete struct S_Particle. A consteval function or block can use a vector type, which is perhaps surprising as a vector allocates heap memory. This is allowed if no attempt is made to store that heap memory (i.e. the vector) in a variable. In other words, temporary heap allocations are allowed in a consteval function or block:
Recommended by LinkedIn
consteval {
using namespace std::meta;
std::vector<info> members;
auto field_x = data_member_spec(^^float, {.name = "x"});
auto field_y = data_member_spec(^^float, {.name = "y"});
auto field_id = data_member_spec(^^int, {.name = "id"});
members.push_back(field_x);
members.push_back(field_y);
members.push_back(field_id);
define_aggregate(^^S_Particle, members);
}
As a reminder, the ^^ operator reflects information about a type (among other things).
The elements of the members vector are std::meta::info objects, which allow you to store the type information and the name of the field. The three info objects are then pushed into the vector.
Finally, define_aggregate adds the fields to the incomplete struct S_Particle.
Main function
Within the main function, the fields can now be used in the same way as an ordinary struct:
int main() {
S_Particle g{1.0f, 2.0f, 42};
std::cout << g.x << ", " << g.y << ", " << g.id << "\n";
}
Conclusion
define_aggregate is again a powerful function in the toolchest. Without code injection (planned for C++29) there are limitations but it is already useful in a number of cases:
Link to godbolt example : https://godbolt.org/z/4szrWWdh9