3

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?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
AJMansfield
  • 4,039
  • 3
  • 29
  • 50
  • Is the body of the functions just the call to `printf`, or something more complicated? If it's just the `printf`, the forget the macro and just call printf`. Can you show a more concrete example of what you want to do? – dbush Jan 16 '18 at 15:42
  • @dbush It's something quite a bit more more complicated. I'm actually using `snprintf` in this case, it's called in multiple places, and the function also has a bunch of mutex locking, memory allocation, and file handling involved. There's just not a simple way to refactor the operation out into normal functions. – AJMansfield Jan 16 '18 at 15:54
  • If the multiple functions you define are similar except for the arguments, then extract the common parts to a separate function, then explicitly define the functions for each command with the appropriate arguments and have them call the common function. The common function can then take a varargs parameter and you would use `vsnprintf` to print. – dbush Jan 16 '18 at 15:57
  • I do not see any use of this macro thing here. Defining millions of functions just to have something different when you call it, use he inline function with the appropriate logic + vnsptintf as dbush mentioned. Now even if you get to the point when it compiles you will have a non debuggable monstet – 0___________ Jan 16 '18 at 16:17

1 Answers1

6

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)
KamilCuk
  • 120,984
  • 8
  • 59
  • 111