4

I want to implement a macro named PRINT which gets zero or more parameters, and does the following:

  1. If it gets zero parameters - do nothing.
  2. If it gets one or more arguments - act like printf.

I succeed in implementing it as you can see in my code below, but only at the cost of calling to printf with an empty string in the case we get zero arguments.

Is there a way I can handle the zero arguments case without calling to printf (it's not efficient to print something when you just want to do nothing)?

#include <stdio.h>

#define PRINT(...) printf("" __VA_ARGS__);

int main(){
    PRINT();
    PRINT("print\n");
    PRINT("print number: %d\n", 7);
    return 0;
}   

output:

print
print number: 7  
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
Rodrigo
  • 167
  • 2
  • 9
  • 2
    Where does the empty `PRINT()` come from? From another macro? (Any human programmer could probably just delete it.) – M Oehm Nov 14 '18 at 10:37
  • [Jens Gustedt](https://stackoverflow.com/users/366377/jens-gustedt) [answered](https://stackoverflow.com/questions/3420459/c-preprocessor-macro-overloading) a question on detecting an empty argument list and [showed how](https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/). It is quite involved. – Eric Postpischil Nov 15 '18 at 00:24

3 Answers3

2

Both gcc and clang will completely eliminate the call to printf in the case that it is passed an empty format string. Probably that optimisation is reasonably common.

See here and here for disassembly on gcc.godbolt.org.

In short, don't worry about it.

rici
  • 234,347
  • 28
  • 237
  • 341
2

If you do want to outsource the check of empty argument list to the macro, then perhaps something like this:

#define HAS_ARGS(...) (sizeof( (char[]){#__VA_ARGS__} ) > 1)

#define PRINT(...) (HAS_ARGS(__VA_ARGS__) ? printf("" __VA_ARGS__) : (void)0)

This relies on the compound literal getting size 1 in case of an empty string = null terminator only. Example:

#include <stdio.h>

#define HAS_ARGS(...) (sizeof( (char[]){#__VA_ARGS__} ) > 1)

#define PRINT(...) (HAS_ARGS(__VA_ARGS__) ? printf("" __VA_ARGS__) : (void)0)

int main (void)
{
  int i = 5;
  PRINT("%d\n", i);
  PRINT();
}
Lundin
  • 195,001
  • 40
  • 254
  • 396
2

A solution is:

#define Argument1(a,...)    Argument2(a
#define Argument2(a, b,...) b
#define TestEmpty()         ,
#define PRINT(...)          Argument1(TestEmpty __VA_ARGS__ (),), printf(__VA_ARGS__);,)

For this source text:

Test 0: PRINT()
Test 1: PRINT("Hello, world.\n")
Test 2: PRINT("Result is %d.\n", result)
Test 3: PRINT("%d = %g.\n", 3, 3.)

the result of preprocessing is:

Test 0:
Test 1: printf("Hello, world.\n");
Test 2: printf("Result is %d.\n", result);
Test 3: printf("%d = %g.\n", 3, 3.);

This requires that the arguments to PRINT not start with a parenthesized item, which seems acceptable for this particular question. The code here is not a general solution for detecting an empty parameter when parentheses may be present.

Explanation:

  • In preparing to replace PRINT, __VA_ARGS__ is replaced. TestEmpty is not replaced at this time because it is not followed by parentheses.
  • Then PRINT is replaced, and the result is rescanned.
  • Argument1 is identified for replacement. In preparation for that, its arguments are processed.
  • At this point, if __VA_ARGS__ is empty, we have have the tokens TestEmpty (), which are replaced by ,. Otherwise, TestEmpty <some tokens> () remains. Note that, if TestEmpty () is present, the , it expands to is the first argument to Argument1. It is not an argument separator, because the arguments to Argument1 have already been identified.
  • Then Argument1 is replaced, producing either Argument2(, (if __VA_ARGS was empty) or Argument2(TestEmpty possibly followed by additional tokens (otherwise).
  • The result is either Argument2(,, printf();,) or Argument2(TestEmpty <some tokens>, printf(<some tokens>);,), according to whether __VA_ARGS__ was empty or not.
  • The tokens are now rescanned for further replacement, so the , will now be recognized as an argument separator.
  • Finally, Argument2 is replaced with an empty token list or with printf(<some tokens>);.
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • 1
    Brilliant use of the preprocessor. But what about Test 4? `char *str = "This is a string.\n"; PRINT(str);` Or Test 5? `char *str = ""; PRINT(str);` I'd also be very concerned about maintainability and loathe to release those macros in the wilds of some large project. – Andrew Henle Nov 15 '18 at 12:46
  • @AndrewHenle: Your proposed tests 4 and 5 result in `printf(str);`, as desired. As long as the first argument does not start with a parentheses, I think the macros will work. If they start with any non-empty parentheses, an error should be generated, so the problem will be caught. It is rare for `printf` arguments to start with parentheses. I suspect the most likely case would be something like `PRINT(SomeMacroToPrepareFormatString(stuff),…)`, where the macro expands to a parenthesized sequence (out of the common habit of parenthesizing macro replacement text, not necessary in this case… – Eric Postpischil Nov 15 '18 at 13:49
  • @AndrewHenle: … unless the replacement text contains a comma operator that needs to be parenthesized to prevent it from being interpreted as an argument separator). I would not recommend this for a large project; I am just answering the Stack Overflow question because knowledge is valuable. – Eric Postpischil Nov 15 '18 at 13:50