This can be done using "macromagy," as you suggested:
For each structure, create a header file ( mystruct-fields.h ) as follows:
FIELD(int, field1) FIELD(int*, field2) FIELD(char*, string1)
Then in another header ( mystruct.h ) you turn on as many times as you need:
#define FIELD(T,N) TN; struct mystruct { #include "mystruct-fields.h" }; #undef FIELD #define FIELD(T,N) { STRINGIFY(T), STRINGIFY(N), offsetof(mystruct, N) }, #define STRINGIFY1(S) #S #define STRINGIFY(S) STRINGIFY1(S) struct mystruct_table { struct { const char *type, *name; size_t offset; } field[]; } table = { #include "mystruct-fields.h" {NULL, NULL, 0} }; #undef FIELD
You can then implement your reflection functions using a table, however you choose.
Perhaps using a different header layer involves reusing the above code for any structure without overwriting it, so your top-level code can only say something like:
#define STRUCT_NAME mystruct #include "reflectable-struct.h" #undef STRUCT_NAME
Honestly, it is easier for people who come after you if you just write the structure usually and then write the table out manually; it's much easier to read, your IDE will be able to automatically populate your types, and noticeable warnings in the comments should help prevent people breaking it in the future (and in any case, do you have tests for this right?)