I need to have meta-information about structures in my code. So, I've code some combination of C structures (to store meta information) and C preprocessor macros to initialize these structures without much of boilerplate code. Now it looks like this (really, I'm storing a lot more information about fields, but this code is enough for my question):
#include <stdint.h>
#include <stdlib.h>
#include <stddef.h>
struct meta_field {
const char *name;
size_t offset;
};
struct meta_struct {
const char *name;
size_t size;
struct meta_field fields[];
};
#define META_STRUCT_BEGIN(NAME) \
static struct meta_struct s_meta_##NAME = { \
.name = "" #NAME, \
.size = sizeof(struct NAME), \
.fields = {
#define META_STRUCT_END() ,{NULL, 0}}}
#define META_FIELD(NAME) { .name = "" #NAME, .offset = offsetof(struct CURRENT_META_STRUCT, NAME) }
struct example {
int i;
double d;
};
#define CURRENT_META_STRUCT example
META_STRUCT_BEGIN(CURRENT_META_STRUCT)
META_FIELD(i),
META_FIELD(d)
META_STRUCT_END();
#undef CURRENT_META_STRUCT
It works. But it has a lot of boilerplate still: #define CURRENT_META_STRUCT
, usage of CURRENT_META_STRUCT
as argument to META_STRUCT_BEGIN()
, #undef
for CURRENT_META_STRUCT
— all of this looks ugly, IMHO.
I know, that it is impossible to define macro by macro (so, META_STRUCT_BEGIN()
can not define CURRENT_META_STRUCT
). But looking at Branf*ck implementation in C Preprocessor and Boost-PP, I think that it is posisble to implement something which will look like this:
META_STRUCT(example,
META_FIELD(i),
META_FIELD(d)
)
But I can not wrap my head around this preprocessor magic.
Could somebody helps me?
I've read https://github.com/orangeduck/CPP_COMPLETE already, but it didn't help.
Also, it was marked as duplicate of this, but problem is I need to add "default" argument to all generated macro "calls".
Here are some sketch, what I want to achieve:
#define META_FIELD_IMPL(STRUCT, NAME) { .name = "" #NAME, .offset = offsetof(struct STRUCT, NAME) }
#define META_FIELD(NAME) NAME
#define META_STRUCT(NAME, ... ) \
static struct meta_struct s_meta_##NAME = { \
.name = "" #NAME, \
.size = sizeof(struct NAME), \
.fields = { \
/* Each ARG from __VA_ARGS__ is META_FIELD(x) and \
* I need to call META_FIELD_IMPL(NAME, <Expansion of META_FIELD>) \
* for each ARG from _VA_ARGS_ here */ \
{NULL, 0} \
}}
Now in this example there is only one argument for META_FIELD()
: NAME
, but in real system there are 6 arguments for META_FIELD()
and some helper macro which provide "default" values for common cases, so META_FIELD()
is necessary and could not be replaced by NAME
itself.
And one last complication: such META_STRUCT()
could be called as argument to META_FIELD()
, because some fields contains pointers to nested meta-structs! Now it is done by declaring named objects for all nested sub-structures, but I want to avoid it too! I understand, that depth of nesting could be limited by some arbitrary constant, it is Ok.
Update: I added this example of what I want to type and what I want to get after preprocessor. I can not figure out how to implement all these macros (marked with /* ??? */
). And, yes, I've checked, manually-coded "end result" compiles fine.
enum meta_type { mt_end, mt_int, mt_string, mt_struct };
struct meta_struct;
struct meta_field {
const char *name;
enum meta_type type;
size_t offset;
struct meta_struct *child;
};
struct meta_struct {
const char *name;
size_t size;
struct meta_field *fields;
};
#define META_STRUCT(NAME, ...) static struct meta_struct s_meta_##name = { .name = #NAME, .size = sizeof(struct NAME), .fileds = (struct meta_field[]){ /* ??? */, {NULL, mt_end, 0, NULL}}}
#define META_FIELD_IMPL0(STRUCT, NAME, TYPE, CHILD) { .name = #NAME, .type = TYPE, .offset = offsetof(STRUCT, NAME), .child = CHILD }
#define META_FIELD_IMPL1(NAME, TYPE, CHILD) /* ??? */
#define META_FIELD(NAME, TYPE) META_FIELD_IMPL1(NAME, TYPE, NULL)
#define META_FIELD_SUB(NAME, CHILD) META_FIELD_IMPL1(NAME, mt_struct, CHILD)
#define META_SUBSTRUCT(NAME, ...) (struct meta_struct){ .name = #NAME, .size = sizeof(struct NAME), .fileds = (struct meta_field[]){ /* ??? */, {NULL, mt_end, 0, NULL}}}
/* Example of "input": */
struct child {
int i;
};
struct parent {
int i;
struct child c;
const char *s;
};
META_STRUCT(parent,
META_FIELD(i, mt_int),
META_FIELD_SUB(c,
META_SUBSTRUCT(child,
META_FIELD(i, mt_int)
)
),
META_FIELD(s, mt_string)
);
/* This should give this */
static struct meta_struct s_meta_parent = {
.name = "parent",
.size = sizeof(struct parent),
.fields = (struct meta_field[]) {
{ .name = "i", .type = mt_int, .offset = offsetof(struct parent, i), .child = NULL },
{ .name = "c", .type = mt_struct, .offset = offsetof(struct parent, c), .child = &(struct meta_struct){
.name = "child",
.size = sizeof(struct child),
.fields = (struct meta_field[]) {
{ .name = "i", .type = mt_int, .offset = offsetof(struct child, i), .child = NULL },
{NULL, mt_end, 0, NULL}
}
}
},
{ .name = "s", .type = mt_string, .offset = offsetof(struct parent, s), .child = NULL },
{NULL, mt_end, 0, NULL}
}
};
Here's an approach.
#define GLUE(A,B) GLUE_I(A,B)
#define GLUE_I(A,B) A##B
#define COUNT(...) COUNT_I(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1,)
#define COUNT_I(V,_9,_8,_7,_6,_5,_4,_3,_2,X,...) X
This is an indirect glue macro (allowing arguments to expand) and a count macro. The count macro here supports only 9 arguments, but it's easy to expand the pattern. This just expands to the number of arguments passed. I'm assuming you've seen this before, but just in case you haven't, this just works by argument shifting; it indirectly expands the arguments, and each extra argument shift above 1 displaces the number list until something is "above" X, which it expands to.
#define FIRST(...) FIRST_I(__VA_ARGS__,)
#define FIRST_I(X,...) X
#define REST(X,...) __VA_ARGS__
#define EXPAND(...) __VA_ARGS__
...these are operations on tuples. For the purposes of this answer, a tuple is a set of tokens in parentheses. FIRST
and REST
use tuples as data types (should be obvious; FIRST
indirectly grabs the first element, REST
expands to everything but that). EXPAND
unwraps a tuple.
And the next utility macro:
#define FOREACH(MACRO_,DATA_,TUPLE_) GLUE(FOREACH_I_,COUNT TUPLE_)(MACRO_,DATA_,TUPLE_)
#define FOREACH_I_1(MACRO_,DATA_,TUPLE_) MACRO_(DATA_,FIRST TUPLE_)
#define FOREACH_I_2(MACRO_,DATA_,TUPLE_) MACRO_(DATA_,FIRST TUPLE_) FOREACH_I_1(MACRO_,DATA_,(REST TUPLE_))
#define FOREACH_I_3(MACRO_,DATA_,TUPLE_) MACRO_(DATA_,FIRST TUPLE_) FOREACH_I_2(MACRO_,DATA_,(REST TUPLE_))
#define FOREACH_I_4(MACRO_,DATA_,TUPLE_) MACRO_(DATA_,FIRST TUPLE_) FOREACH_I_3(MACRO_,DATA_,(REST TUPLE_))
#define FOREACH_I_5(MACRO_,DATA_,TUPLE_) MACRO_(DATA_,FIRST TUPLE_) FOREACH_I_4(MACRO_,DATA_,(REST TUPLE_))
#define FOREACH_I_6(MACRO_,DATA_,TUPLE_) MACRO_(DATA_,FIRST TUPLE_) FOREACH_I_5(MACRO_,DATA_,(REST TUPLE_))
#define FOREACH_I_7(MACRO_,DATA_,TUPLE_) MACRO_(DATA_,FIRST TUPLE_) FOREACH_I_6(MACRO_,DATA_,(REST TUPLE_))
#define FOREACH_I_8(MACRO_,DATA_,TUPLE_) MACRO_(DATA_,FIRST TUPLE_) FOREACH_I_7(MACRO_,DATA_,(REST TUPLE_))
#define FOREACH_I_9(MACRO_,DATA_,TUPLE_) MACRO_(DATA_,FIRST TUPLE_) FOREACH_I_8(MACRO_,DATA_,(REST TUPLE_))
This is the key macro; it is in practice used to apply your structure's type into the lower level macros for each field. Again, this particular code only works with 9 fields per level, but it should be obvious how to extend this (you probably want to support as many levels as your COUNT
macro can count to).
The third argument of FOREACH
should be a tuple; FOREACH
's job is to call your macro on DATA_
and each element of TUPLE_
. For example, FOREACH(FIELD,parent,(i,j,k))
expands to FIELD(parent,i) FIELD(parent,j) FIELD(parent,k)
.
The core calling structure for the specific utilities uses tuple data types; when used with the FOREACH
macro, underlying calls will curry the outer structure type into each call appropriately. The calling structure looks like this:
META_STRUCT(type , FIELD[ , FIELD[ ... ] ] )
...where each FIELD
is a 3-tuple:
(name , type , CHILD )
...and each CHILD
is a tuple, which is expanded into a .child
assignment. META_SUBSTRUCT
works similarly. So according to this call structure, your example will actually wind up being called like this:
META_STRUCT(parent, (i, mt_int, (NULL)), (c, mt_struct, META_SUBSTRUCT(child,(i,mt_int,(NULL)))),(s, mt_string, (NULL)));
In practice, META_SUBSTRUCT
will actually fully expand. Its expansion will be a "tuple of tokens" that acts as the child assignment; that is, META_SUBSTRUCT
will expand to the actual assignments in the substruct surrounded by parentheses. The FOREACH
macro is applied to this construct, currying the outer structure and the three arguments of the above 3-tuple into the 4-argument macro APPLY_UTILI_FIELD
.
The usage mirrors your usage and adopts to this call structure. For example, META_FIELD(i, mt_int)
is just a macro that expands to (i, mt_int, (NULL))
.
#define META_STRUCT_BEGIN(NAME_) \
static struct meta_struct s_meta_##NAME_ = { \
.name = #NAME_, \
.size = sizeof(struct NAME_), \
.fields = (struct meta_field[]) {
#define META_STRUCT(NAME_,...) \
META_STRUCT_BEGIN(NAME_) \
FOREACH(APPLY_META_STRUCT_MACRO,NAME_,(__VA_ARGS__)) \
META_STRUCT_END()
#define META_STRUCT_END() \
{NULL, mt_end, 0, NULL}}}
#define META_SUBSTRUCT(NAME_,...) \
( META_SUBSTRUCT_BEGIN(NAME_) \
FOREACH(APPLY_META_STRUCT_MACRO,NAME_,(__VA_ARGS__)) \
META_SUBSTRUCT_END() )
#define META_SUBSTRUCT_BEGIN(NAME_) \
&(struct meta_struct) { \
.name = #NAME_, \
.size = sizeof(struct NAME_), \
.fields = (struct meta_field[]) {
#define META_SUBSTRUCT_END() \
{NULL, mt_end, 0, NULL}}}
#define APPLY_META_STRUCT_MACRO(DATA_, ARG_) APPLY_UTIL_FIELD(DATA_, EXPAND ARG_)
#define APPLY_UTIL_FIELD(...) APPLY_UTILI_FIELD(__VA_ARGS__)
#define APPLY_UTILI_FIELD( STRUCT_, FIELD_, TYPE_, CHILD_) \
{ .name = #FIELD_, .type = TYPE_, .offset = offsetof(struct STRUCT_, FIELD_), .child = EXPAND CHILD_ },
#define META_FIELD(NAME_, TYPE_) (NAME_, TYPE_, (NULL))
#define META_FIELD_SUB(NAME_, SUB_) (NAME_, mt_struct, SUB_)
These macros allow nesting indefinitely of META_SUBSTRUCT
calls without running into blue paint, since all such macros wind up being evaluated in the argument substitution phase (...
argument mentioning __VA_ARGS__
causes all arguments to expand; each level of META_SUBSTRUCT
is a top level expansion in this scenario; this condition recurses).
Given you're showing an example with less fields than you're using, you need specifically to tweak your user defined META_FIELD
, META_FIELD_SUB
, etc macros to produce k
-tuples instead of 3-tuples (where k
is what you need). Then, just adjust APPLY_UTILI_FIELD
's arguments and expansion to match.
This code assumes a standard C preprocessor. If you're working on MSVC specifically (which doesn't have one), it won't work. Specifically, MSVC fails to expand __VA_ARGS__
when it's supposed to; if you care to work on MSVC, change the corresponding macros for these three to:
#define COUNT(...) EXPAND(COUNT_I(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1,))
#define FIRST(...) EXPAND(FIRST_I(__VA_ARGS__,))
#define APPLY_UTIL_FIELD(...) EXPAND(APPLY_UTILI_FIELD(__VA_ARGS__))
The result should also work on standard compliant CPP's.