Home > Back-end >  How can I use this macro in cases that I don't want to pass function as argument?
How can I use this macro in cases that I don't want to pass function as argument?

Time:08-25

given the following Macro in C:

#define helper(arg1, arg2, func) do {\
if ((arg1) == (arg2)) {(arg1)  ; (arg2)  ;}\
func((arg1));\
} while (0)

If sometimes I don't want to pass func as argument to the macro, how can I call to the macro?
Namely, I want to call to macro like that: helper(x,y,?); such then the line func(arg1); (in the macro) will not be executed. But I don't know how can i do it and what I need to pass to the macro instead of ? ..

Is there a way/trick to do is with the above macro?

I thought to write dummy function like that:

void dummy_function(int arg) { return; } 

and to use that in these cases helper(x,y,dummy_function). But I looking for another and more elegant trick if there is one. without the need to write such a function.

CodePudding user response:

Edit: Macros for such purpose are questionable, a way better solution is using functions instead, they provide way better type safety and other andvantages – how to deal with see Lundin's answer!

You could simply pass a cast to void as function argument:

helper(someVariable, anotherOne, (void));

If you want to have it a bit more convenient you could help out with a variadic macro:

#define helper(...) helper_(__VA_ARGS__, (void), )
// final empty token prevents empty __VA_ARGS__ within helper_ (!)

#define helper_(arg1, arg2, function, ...) // ...

though this comes with the disadvantage that surplus arguments simply are discarded (while passing too view would be evaluated to ((void)) and thus would fail to compile).

Beware, too, that this would fail, too, on MSVC, as this one incorrectly interprets the variadic arguments as one single token.

CodePudding user response:

Disclaimer: macros like these are a terrible idea and you should seriously consider writing a function like

void func (int* arg1, int* arg2, void(*func)(int) );

where func is set to NULL when not used. Or alternatively as someone suggested, pass on a function that does nothing. All of that is perfectly fine design.

Macros or not, you definitely need to drop the idea of passing a variable seemingly by value and then have that one changed. That's completely unexpected by any C programmer - don't go inventing some private macro language very different from C.


As for how to not do it properly with macros, like you asked... here is one way which is fairly type safe despite being an evil macro:

  • Make a variadic macro.
  • It is handy to use compound literals inside macros to create temporary objects that will get optimized away.
  • We could invent an internal struct type corresponding to your macro argument list, then initialize it using the variadic macro list __VA_ARGS__. This gives a bit of type safety since argument types must match the struct and the number of arguments can't be more than the number of struct members.
  • Initializer lists need to init all members of a struct but can leave some blank, solving the ambiguity with the number of parameters passed.

Example:

typedef struct
{
  int* arg1;
  int* arg2;
  void (*func) (int);
} internal_struct;

#define helper(...)                                        \
do {                                                       \
  if( *(internal_struct){__VA_ARGS__}.arg1 ==              \
      *(internal_struct){__VA_ARGS__}.arg2 )               \
  {                                                        \
      *(internal_struct){__VA_ARGS__}.arg1;                \
      *(internal_struct){__VA_ARGS__}.arg2;                \
  }                                                        \
  (internal_struct){__VA_ARGS__}.func &&                   \
  ((internal_struct){__VA_ARGS__}.func(*(internal_struct){__VA_ARGS__}.arg1),1); \
} while(0)

Here the internal struct compound literal will either get func set to a function pointer, if one is passed, or to NULL (by the static storage initialization rules) if it isn't. We can check this with the && operator which guarantees left op evaluation before right op. So if it isn't NULL, execute the function.

Now as it happens the function in this example used void result and we can't have that in a && expression. Hence the trick to use the comma operator first to execute the function, then pass on the dummy value 1 to keep the && operator happy.

Full example:

#include <stdio.h>

typedef struct
{
  int* arg1;
  int* arg2;
  void (*func) (int);
} internal_struct;

#define helper(...)                                        \
do {                                                       \
  if( *(internal_struct){__VA_ARGS__}.arg1 ==              \
      *(internal_struct){__VA_ARGS__}.arg2 )               \
  {                                                        \
      *(internal_struct){__VA_ARGS__}.arg1;                \
      *(internal_struct){__VA_ARGS__}.arg2;                \
  }                                                        \
  (internal_struct){__VA_ARGS__}.func &&                   \
  ((internal_struct){__VA_ARGS__}.func(*(internal_struct){__VA_ARGS__}.arg1),1); \
} while(0)


void do_something (int arg)
{
  printf("do something with %d\n", arg);
}

int main (void)
{
  int x=1;
  int y=1;

  helper(&x, &y, do_something);
  printf("x: %d, y: %d\n", x, y);
  helper(&x, &y);
  printf("x: %d, y: %d\n", x, y);
}

Output:

do something with 2
x: 2, y: 2
x: 3, y: 3

Generated disassembly on gcc x86 -O3:

main:
    sub     rsp, 8
    mov     edi, 2
    call    do_something
    mov     edx, 2
    mov     esi, 2
    xor     eax, eax
    mov     edi, OFFSET FLAT:.LC1
    call    printf
    mov     edx, 3
    mov     esi, 3
    xor     eax, eax
    mov     edi, OFFSET FLAT:.LC1
    call    printf
    xor     eax, eax
    add     rsp, 8
    ret

As you can see, the compound literals yield absolutely no overhead.

  • Related