I'm trying to use a macro to define several similar functions based on the macro's parameters. However the number and types of parameters that the resulting function needs to take isn't the same across all of the functions, but I also need to pass all of the function's arguments into another variadic function inside the function's body.
A minimal example of what I'm trying to accomplish:
#define COMMAND(__COMMAND__, __FORMAT__, ...) \
void __COMMAND__ ( __VA_ARGS__ ) { \
printf( __FORMAT__, ##__VA_ARGS__ ); \
}
COMMAND( Start, "m start %c\r", (char) unit )
COMMAND( Home, "m home\r" )
COMMAND( Add_To_Chart, "cv 0 %d %d\r", (int) ch1, (int) ch2 )
// literally hundreds of additional COMMANDs needed here.
(Note that the actual logic of the function is much more complicated.)
However, I can't figure out a syntax that's valid both as the argument list in a function definition and in a function call.
Using the form (type)arg
isn't valid syntax for the function definition, but I can pass it to the printf
just fine (it's treated as a cast).
COMMAND( A, "cv 0 %d %d\r", (int)ch1, (int)ch2 )
// error: expected declaration specifiers or ‘...’ before ‘(’ token
// void A ( (int)ch1, (int)ch2 ) {
// printf( "cv 0 %d %d\r", (int)ch1, (int)ch2 );
// }
Doing it the other way, type(arg)
, appears to work for the function declaration, but function-style casts are only available in C++, not C, so it fails on printf
.
COMMAND( B, "cv 0 %d %d\r", int(ch1), int(ch2) )
// error: expected expression before ‘int’
// void B ( int(ch1), int(ch2) ) {
// printf( "cv 0 %d %d\r", int(ch1), int(ch2) );
// }
How can I use the variadic macro arguments as both the function's parameter definition and as parameters passed to another function?
Do not make __*
identifiers in your code. __COMMAND__
and __FORMAT__
are making your code invalid.
You have to make preprocessor aware of the types. Pass them as separate tokens, then shuffle them for example in separate chains overloaded by number of arguments.
// Create function arguments
#define ARGS_0() void
#define ARGS_2(t1,v1) t1 v1
#define ARGS_4(t1,v1,t2,v2) t1 v1, t2 v2
#define ARGS_N(_4,_3,_2,_1,_0,N,...) ARGS##N
#define ARGS(...) ARGS_N(_0,##__VA_ARGS__,_4,_3,_2,_1,_0)(__VA_ARGS__)
// Pass arguments to printf with a leading comma.
#define PASS_0()
#define PASS_2(t1,v1) , v1
#define PASS_4(t1,v1,t2,v2) , v1, v2
#define PASS_N(_4,_3,_2,_1,_0,N,...) PASS##N
#define PASS(...) PASS_N(_0,##__VA_ARGS__,_4,_3,_2,_1,_0)(__VA_ARGS__)
#define COMMAND(cmd, fmt, ...) \
void cmd(ARGS(__VA_ARGS__)) { \
printf(fmt PASS(__VA_ARGS__)); \
}
COMMAND( Start, "m start %c\r", char, unit)
COMMAND( Home, "m home\r")
COMMAND( Add_To_Chart, "cv 0 %d %d\r", int, ch1, int, ch2)
expands to:
void Start(char unit) { printf("m start %c\r" , unit); }
void Home(void) { printf("m home\r" ); }
void Add_To_Chart(int ch1, int ch2) { printf("cv 0 %d %d\r" , ch1, ch2); }
You can make the code more generic by having a single "apply a macro on every pair of arguments and join them with this and if empty use this" macro overloaded on number of arguments.
#define ESC(...) __VA_ARGS__
#define APPLYTWOJOIN_0(f,j,e) ESC e
#define APPLYTWOJOIN_2(f,j,e,t,v) f(t,v)
#define APPLYTWOJOIN_4(f,j,e,t,v,...) f(t,v) ESC j \
APPLYTWOJOIN_2(f,j,e,__VA_ARGS__)
#define APPLYTWOJOIN_6(f,j,e,t,v,...) f(t,v) ESC j \
APPLYTWOJOIN_4(f,j,e,__VA_ARGS__)
#define APPLYTWOJOIN_8(f,j,e,t,v,...) f(t,v) ESC j \
APPLYTWOJOIN_6(f,j,e,__VA_ARGS__)
// etc.
#define APPLYTWOJOIN_N(_8,_7,_6,_5,_4,_3,_2,_1,_0,N,...) \
APPLYTWOJOIN##N
// For every two arguments in the list, apply function `f(a,b)` on it.
// Join every result of that function with `ESC j`.
// Expand empty result to `ESC e`.
#define APPLYTWOJOIN(f, j, e, ...) \
APPLYTWOJOIN_N(_0,##__VA_ARGS__,_8,_7,_6,_5,_4,_3,_2,_1,_0)\
(f,j,e,##__VA_ARGS__)
// Pass argument to printf. The leading comma is after format string.
#define PASS(t,v) , v
// Pass arguments in function parameter list.
#define ARGS(t,v) t v
#define COMMAND(cmd, fmt, ...) \
void cmd( APPLYTWOJOIN(ARGS, (,), (void), ##__VA_ARGS__) ) { \
printf(fmt APPLYTWOJOIN(PASS, (), (), ##__VA_ARGS__) ); \
}
COMMAND( Start, "m start %c\r", char, unit)
COMMAND( Home, "m home\r")
COMMAND( Add_To_Chart, "cv 0 %d %d\r", int, ch1, int, ch2)