1
votes

I have many macros that end up generating code. For example:

#define CODE_GEN_IDENT1_HDR(PARAM1, PARAM2, PARAM3) \
   // code generated

#define CODE_GEN_IDENT2_HDR(PARAM1, PARAM2, PARAM3) \
   // code generated

#define CODE_GEN_IDENT1_SRC(PARAM1, PARAM2, PARAM3) \
    // code generated

#define CODE_GEN_IDENT2_SRC(PARAM1, PARAM2, PARAM3) \
    // code generated

The idea is that HDR generates functions definitions and SRC generates their implementation. All macros have the same amount of arguments (in this example, 3). IDENT can be any name like MATH, TRIG, ALGS, CONTAINERS, etc. This is what I want to focus on.

I'm trying to build up a macro that can generate all of these macros macros with different IDENT using variadic macros. For example:

// Concatenate macros to a standard form
#define CONCATH_(C) CODE_GEN_##C##_HDR
#define CONCATC_(C) CODE_GEN_##C##_SRC

// CONCATH concatenates to HDR
// CONCATC concatenates to SRC
#define CONCATH(C) CONCATH_(C)
#define CONCATC(C) CONCATC_(C)

#define MASTER_MACRO(PARAM1, PARAM2, PARAM3, ...) \
    // Code that generates all other macros
    // using CONCATH and CONCATC
    // how could this be done?

When I write:

MASTER_MACRO(int, "Hello", char *, MATH, TRIG, CONT)

I would like to have something like:

CODE_GEN_MATH_HDR(int, "Hello", char *)
CODE_GEN_TRIG_HDR(int, "Hello", char *)
CODE_GEN_CONT_HDR(int, "Hello", char *)
CODE_GEN_MATH_SRC(int, "Hello", char *)
CODE_GEN_TRIG_SRC(int, "Hello", char *)
CODE_GEN_CONT_SRC(int, "Hello", char *)

To somehow access the given arguments and concatenate each, making both header and source.

What I currently have is a fixed length macro like:

MASTER_MACRO(PARAM1, PARAM2, PARAM3, MATH, TRIG, CONT, DUPL, SORT) \
    CONCATH(MATH)(PARAM1, PARAM2, PARAM3)
    CONCATH(TRIG)(PARAM1, PARAM2, PARAM3)
    ...
    CONCATC(MATH)(PARAM1, PARAM2, PARAM3)
    ...

And when the user doesn't want to generate CONT, DUPL or any other macro he's got to pass in a pre-defined argumet like _ meaning he is not wanting to generate that and having an empty macro:

#define CODE_GEN___SRC(PARAM1, PARAM2, PARAM3) // Empty

But this is not good enough. On different projects these different IDENT have different names, so having to make new master macros are a bit annoying.

But the big questions are:

  • Can this be done?
  • Is there a simple way to do this?
  • Can it be done using only standard C (no compiler-dependent macro)?
1

1 Answers

1
votes

Yes, you can do this. For a hand rolled implementation, you probably want to start with a basic argument counter like this:

#define COUNT(...) \
        COUNT_I(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1,)
#define COUNT_I(_          ,_9,_8,_7,_6,_5,_4,_3,_2, X,...) X

The argument counter works kind of like a "shift register", injecting the argument list before the count. If called with one argument, everything aligns X with 1. Each additional argument shifts this list over... 2 arguments shifts 2 into X, 3 shifts 3 in, and so on. This is just the basic form, supporting up to 9 arguments, to convey the idea.

...now you can generate variadic macro utilities like this one:

#define GLUE(A,B) GLUE_I(A,B)
#define GLUE_I(A,B) A##B
#define TRANSFORM_CD(MACRO, ...) GLUE(TRANSFORM_CD_,COUNT(__VA_ARGS__))(MACRO,__VA_ARGS__)

#define TRANSFORM_CD_1(MACRO,X) MACRO(X)
#define TRANSFORM_CD_2(MACRO,X,...) MACRO(X),TRANSFORM_CD_1(MACRO,__VA_ARGS__)
#define TRANSFORM_CD_3(MACRO,X,...) MACRO(X),TRANSFORM_CD_2(MACRO,__VA_ARGS__)
#define TRANSFORM_CD_4(MACRO,X,...) MACRO(X),TRANSFORM_CD_3(MACRO,__VA_ARGS__)
#define TRANSFORM_CD_5(MACRO,X,...) MACRO(X),TRANSFORM_CD_4(MACRO,__VA_ARGS__)
#define TRANSFORM_CD_6(MACRO,X,...) MACRO(X),TRANSFORM_CD_5(MACRO,__VA_ARGS__)
#define TRANSFORM_CD_7(MACRO,X,...) MACRO(X),TRANSFORM_CD_6(MACRO,__VA_ARGS__)
#define TRANSFORM_CD_8(MACRO,X,...) MACRO(X),TRANSFORM_CD_7(MACRO,__VA_ARGS__)
#define TRANSFORM_CD_9(MACRO,X,...) MACRO(X),TRANSFORM_CD_8(MACRO,__VA_ARGS__)

Conceptually TRANSFORM_CD is meant to "transform" a Comma Delimited list (args 2 and up) to another comma delimited list by applying a macro to it. In this case, we start with a comma delimited list of base names (what you call IDENT here) and apply one of your transform macros to it; for example, TRANSFORM(CONCATH, TRIG, CONT, DUPL) expands to CODE_GEN_TRIG_HDR, CODE_GEN_CONT_HDR, CODE_GEN_DUPL_HDR.

Next we need something to generate calls with multiple macros and the same parameter set; like this utility:

#define INVOKE_ALL(TUPLE_, ...) GLUE(INVOKE_ALL_,COUNT(__VA_ARGS__))(TUPLE_,__VA_ARGS__)

#define INVOKE_ALL_1(TUPLE_, X) X TUPLE_
#define INVOKE_ALL_2(TUPLE_, X,...) X TUPLE_ INVOKE_ALL_1(TUPLE_,__VA_ARGS__)
#define INVOKE_ALL_3(TUPLE_, X,...) X TUPLE_ INVOKE_ALL_2(TUPLE_,__VA_ARGS__)
#define INVOKE_ALL_4(TUPLE_, X,...) X TUPLE_ INVOKE_ALL_3(TUPLE_,__VA_ARGS__)
#define INVOKE_ALL_5(TUPLE_, X,...) X TUPLE_ INVOKE_ALL_4(TUPLE_,__VA_ARGS__)
#define INVOKE_ALL_6(TUPLE_, X,...) X TUPLE_ INVOKE_ALL_5(TUPLE_,__VA_ARGS__)
#define INVOKE_ALL_7(TUPLE_, X,...) X TUPLE_ INVOKE_ALL_6(TUPLE_,__VA_ARGS__)
#define INVOKE_ALL_8(TUPLE_, X,...) X TUPLE_ INVOKE_ALL_7(TUPLE_,__VA_ARGS__)
#define INVOKE_ALL_9(TUPLE_, X,...) X TUPLE_ INVOKE_ALL_8(TUPLE_,__VA_ARGS__)

Here, TUPLE_ is meant to be a parenthesized argument list (this makes the utility a bit more generic than requiring it support exactly three parameters); and each other parameter represents a macro to be called with those arguments.

Combining the two, this:

INVOKE_ALL((p1 a1, p2 a2, p3 a3),TRANSFORM_CD(CONCATH,MATH,TRIG,CONT,DUPL))

...should expand to (reformatted for clarity):

CODE_GEN_TRIG_HDR(p1 a1, p2 a2, p3 a3)
CODE_GEN_CONT_HDR(p1 a1, p2 a2, p3 a3)
CODE_GEN_DUPL_HDR(p1 a1, p2 a2, p3 a3)

...and if you really want, you could preserve MASTER_MACRO's form and function, simply making it variadic, like this:

#define MASTER_MACRO(PARAM1, PARAM2, PARAM3, ...) \
    INVOKE_ALL((PARAM1, PARAM2, PARAM3),TRANSFORM_CD(CONCATH,__VA_ARGS__)) \
    INVOKE_ALL((PARAM1, PARAM2, PARAM3),TRANSFORM_CD(CONCATC,__VA_ARGS__))

demo at stacked-crooked