aganm
aganm

Reputation: 1379

How do I make a C macro expand to a struct and function using this method

I want to have a macro

#define MY_STRUCT( /* ... /*) /* ... */

That I want to call this way

MY_STRUCT(point, double x, int y);

Which expands to this

typedef struct {
    double x;
    int y;
} point;

void init_point(point *p) {
     p->x = load_double_from_somewhere();
     p->y = load_int_from_somewhere();
}

The macro should be able to handle any number of parameters I give it. I'm trying to generate data bindings for model objects automatically through macros for I've been writing mapping functions by hand and it's tiresome and repetitive (xml to C struct). I know it's possible but I can't figure out how.

Upvotes: 3

Views: 2213

Answers (4)

rici
rici

Reputation: 241971

Note: I'm only addressing the question of how to write the struct definition. I don't see a way to get the preprocessor to automatically generate serialisation and deserialisation code, for a couple of reasons. First, it's not possible to extract a token --the member name-- from a macro argument, even if you could know which token to extract. Second, you could use C11's _Generic feature to create calls to pre-defined type-specific serialisation/deserialisation functions. But the _Generic call needs a complete list of possibilites, so it's not going to be possible to incrementally build up the possibilities. I think you'll find that existing systems which autogenerate serialisation/deserialisation code depend on external code generators. That's a much more flexible (and probably easier) strategy.


So, back to autogenerating struct declarations. As has been mentioned, this is really only possible if you generate N macros, one for every number of arguments. And you also need a way to compute N, which might require more automatically-generated macros. All in all, it's a lot easier to use a framework like Jens Gustedt's P99.

Here's a highly simplified illustration of how to do it, with a small limit of fields because the patterns are obvious.

We start with a macro which will compute the number of arguments in a variadic call. Note that this will note correctly return 0 if there are no variadic arguments. A robust solution, like P99, would handle that correctly. But in this case, it doesn't matter because a struct with no members is not allowed by C, so we know there must be at least one argument. (That wouldn't be true in C++.)

#define ARG9_(a1, a2, a3, a4, a5, a6, a7, a8, a9, ...) a9
#define NARGS(...) ARG9_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)

Now, we need eight macros which construct the members. All they have to do is insert semicolons between the arguments, so it's not very challenging:

#define S8_(first, ...) first; S7_(__VA_ARGS__)
#define S7_(first, ...) first; S6_(__VA_ARGS__)
#define S6_(first, ...) first; S5_(__VA_ARGS__)
#define S5_(first, ...) first; S4_(__VA_ARGS__)
#define S4_(first, ...) first; S3_(__VA_ARGS__)
#define S3_(first, ...) first; S2_(__VA_ARGS__)
#define S2_(first, ...) first; S1_(__VA_ARGS__)
#define S1_(first) first;

Finally, we need to put all that together. To do so, we're going to need a macro which can concatenate up a macro name:

#define PASTE3(a, b, c) PASTE3_(a, b, c)
#define PASTE3_(a, b, c) a ## b ## c

Finally, the macro which creates the struct

#define MY_STRUCT(name, ...)                       \
   typedef struct name name;                       \
   struct name {                                   \
     PASTE3(S, NARGS(__VA_ARGS__), _)(__VA_ARGS__) \
   };

And now we can give it all a try:

MY_STRUCT(s1, int a);
MY_STRUCT(s2, int a, double b);
MY_STRUCT(s3, const char* s, int t[17], double sum);
MY_STRUCT(s4, char a, char b, char c, char d);
MY_STRUCT(s5, char a, char b, char c, char d, char e);
MY_STRUCT(s6, char a, char b, char c, char d, char e, char f);
MY_STRUCT(s7, char a, char b, char c, char d, char e, char f, char g);
MY_STRUCT(s8, char a, char b, char c, char d, char e, char f, char g, short h);

Here's what gcc -E produces, given all of the above: (Note: I can't comment on whether this works on various versions of MSVC. But it's all standard C99.)

# 1 "nargs.h"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "nargs.h"
# 22 "nargs.h"
typedef struct s1 s1; struct s1 { int a; };
typedef struct s2 s2; struct s2 { int a; double b; };
typedef struct s3 s3; struct s3 { const char* s; int t[17]; double sum; };
typedef struct s4 s4; struct s4 { char a; char b; char c; char d; };
typedef struct s5 s5; struct s5 { char a; char b; char c; char d; char e; };
typedef struct s6 s6; struct s6 { char a; char b; char c; char d; char e; char f; };
typedef struct s7 s7; struct s7 { char a; char b; char c; char d; char e; char f; char g; };
typedef struct s8 s8; struct s8 { char a; char b; char c; char d; char e; char f; char g; short h; };

Upvotes: 2

user10678532
user10678532

Reputation:

If you want to autogenerate source code, the preprocessor is the worst tool you can get.

And you absolutely cannot create any macro to generate a struct with any number of fields; the C preprocessor does not support loops or recursion.

But if a maximum of 8 fields/arguments are enough, though NON-STANDARD, this will do in gcc and clang:

#define MY_STRUCT(name, ...)  struct name { C2SC(__VA_ARGS__,,,,,,,,,) }
#define C2SC(a, b, c, d, e, f, g, h, ...)       a; b; c; d; e; f; g; h;

MY_STRUCT(foo, int bar, int baz, void *quux, void *xuq, char sq[12]);

This deliberately creates a struct name { ... }, not a typedef struct { ... } name as in the OP: It's already obtuse enough even without a MY_STRUCT macro which actually generates a typedef.

Upvotes: 1

HolyBlackCat
HolyBlackCat

Reputation: 96924

I'm not sure if it's possible to extract x from double x (unless you know all possible types your variables can have).

Also, iterating over comma-separated lists using preprocessor requires writing boilerplate macros (O(n) of them, I think). Boost.Preprocessor might help with that, but you can completely avoid boilerplate if you use a different syntax:

MY_STRUCT(point, (double,x)(double,y))

Implementation:

#define MY_STRUCT(name, seq) \
    typedef struct { \
        MY_STRUCT_impl_end(MY_STRUCT_impl_decl_loop_a seq) \
    } name; \
    void init_point(name *p) { \
        MY_STRUCT_impl_end(MY_STRUCT_impl_load_loop_a seq) \
    }

#define MY_STRUCT_impl_end(...) MY_STRUCT_impl_end_(__VA_ARGS__)
#define MY_STRUCT_impl_end_(...) __VA_ARGS__##_end

#define MY_STRUCT_impl_decl_loop(type, name) type name;
#define MY_STRUCT_impl_decl_loop_a(...) MY_STRUCT_impl_decl_loop(__VA_ARGS__) MY_STRUCT_impl_decl_loop_b
#define MY_STRUCT_impl_decl_loop_b(...) MY_STRUCT_impl_decl_loop(__VA_ARGS__) MY_STRUCT_impl_decl_loop_a
#define MY_STRUCT_impl_decl_loop_a_end
#define MY_STRUCT_impl_decl_loop_b_end

#define MY_STRUCT_impl_load_loop(type, name) p->name = load_##name##_from_somewhere();
#define MY_STRUCT_impl_load_loop_a(...) MY_STRUCT_impl_load_loop(__VA_ARGS__) MY_STRUCT_impl_load_loop_b
#define MY_STRUCT_impl_load_loop_b(...) MY_STRUCT_impl_load_loop(__VA_ARGS__) MY_STRUCT_impl_load_loop_a
#define MY_STRUCT_impl_load_loop_a_end
#define MY_STRUCT_impl_load_loop_b_end

Upvotes: 1

0___________
0___________

Reputation: 68089

I do not know what you need it for, but ...

#define x(a,b,c)    struct a {b;c;}

x(mystr, int m, double n);

and the result is:

struct mystr {int m;double n;};

or

#define x(a,b,c)    typedef struct {b;c;} a

x(mystr, int m, double n);

and the result

typedef struct {int m;double n;} mystr;

Upvotes: 1

Related Questions