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}
  }
};
 
     
    