0

I have macros that will either expand into a number (in my example, cases 2 and 4), or nothing (in my example, cases 1 and 3). Nothingness isn't desirable for me, so I need other macros that will detect and replace the nothingness.

I was able to create a macro, OLD_CHECK, that would detect the nothingness-case and replace it with a 0. (Case 1)

I want to improve on it and create a new macro, NEW_CHECK, that will detect the nothingness-case and replace it with a default value. (Case 3)

/* what I currently have */
print("case 1: [%d]", OLD_CHECK( ));                   // case 1: [0]
print("case 2: [%d]", OLD_CHECK(5));                   // case 2: [5]

/* what I hope to achieve */
int default_value = 7;
print("case 3: [%d]", NEW_CHECK(default_value,  ));    // case 3: [7]
print("case 4: [%d]", NEW_CHECK(default_value, 5));    // case 4: [5]

To write OLD_CHECK, I was using the number of arguments. This method does not seem to feasible to write NEW_CHECK with, as cases 3 and 4 are both detected as having 2 arguments.

Edit: this is the macro that works to catch and handle Cases 1 and 2:

/* Given a dummy and >=1 arguments, expand to the first argument */
#define FIRST_ONE(dummy, a1, ...) a1
/* if "..." is nonblank, expand to the first arg of "...". otherwise, expand to 0 */
#define OLD_CHECK(...) OLD_CHECK_CORE(__VA_ARGS__) 
#define OLD_CHECK_CORE(...) FIRST_ONE(dummy, ##__VA_ARGS__, 0)

Edit 2: It is necessary to have the trailing comma in Case 3. It is an unfortunate byproduct of a series of other more-complicated macros.

ANSWERED: This method, given by p00ya, works for me:

#define NEW_CHECK_HELPER(...) , ## __VA_ARGS__
#define NEW_CHECK(default, ...) (default NEW_CHECK_HELPER(__VA_ARGS__))
Nero
  • 143
  • 1
  • 6

4 Answers4

1

If you really need to support the "empty argument" in case 3, then you can try the non-portable:

#include <stdio.h>

#define NEW_CHECK(default, ...) (default __VA_OPT__(, __VA_ARGS__))

int
main() {
  int default_value = 7;
  printf("case 3: %d\n", NEW_CHECK(default_value, ));
  printf("case 4: %d\n", NEW_CHECK(default_value, 5));
}

When compiled with recent versions of gcc (e.g. 10.1, see https://godbolt.org/z/AhST8m), this will print:

case 3: 7
case 4: 5

It relies on the non-standard __VA_OPT__, which is being proposed for C, but is supported as a GCC extension.

I would question your requirement to include the trailing comma in case 3, though.

p00ya
  • 3,659
  • 19
  • 17
  • Thank you; my problem does need to handle the trailing comma. Unfortunately, the non-portability of the solution is an issue. My environment does not support `__VA_OPT__`, which I tested with both my problem child and with [this](https://stackoverflow.com/a/48045656/13837464). – Nero Jun 30 '20 at 04:03
  • Fair enough; I added a second answer that uses GCC's `,##__VA_ARGS__` syntax. – p00ya Jun 30 '20 at 05:02
1

Using GCC's , ## __VA_ARGS__ extension for comma-deletion (which you are already using for OLD_CHECK):

#include <stdio.h>

#define NEW_CHECK_HELPER(...) , ## __VA_ARGS__
#define NEW_CHECK(default, ...) (default NEW_CHECK_HELPER(__VA_ARGS__))

int
main() {
  int default_value = 7;
  printf("case 3: %d\n", NEW_CHECK(default_value, ));
  printf("case 4: %d\n", NEW_CHECK(default_value, 5));
}

Prints:

case 3: 7
case 4: 5
p00ya
  • 3,659
  • 19
  • 17
0

Your existing method will work if you get rid of the trailing comma in case three:

#define FIRST_ONE(dummy, a1, ...) a1
#define NEW_CHECK(DEFAULT, ...) FIRST_ONE(dummy,##__VA_ARGS__, DEFAULT)

/* what I hope to achieve */
int default_value = 7;
print("case 3: [%d]", NEW_CHECK(default_value  ));    // case 3: [7]
print("case 4: [%d]", NEW_CHECK(default_value, 5));    // case 4: [5]

The reason is that GCC makes a distinction between no argument and an empty argument for ,##__VA_ARGS__ -- only if there is no argument does it remove the comma -- a (single) empty argument keeps it.

Then there's the strangeness that OLD_CHECK( ) is treated as no argument rather than an empty argument, which arguably doesn't make a lot of sense.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
0

After a while, I came up with an alternative many-step solution that is equally portable as OLD_CHECK. It reuses FIRST_ONE from before, and also uses a standard NARGS macro to count the number of arguments in __VA_ARGS__.

/* NEW_CHECK(default, maybe_blank) */
#define NEW_CHECK(...) M_(__VA_ARGS__)
/* delay expansion of M once to perform Q */
#define M_(D, ...) F(D, Q(__VA_ARGS__))
/* Q(maybe_blank): if __VA_ARGS__ is non-blank, __VA_ARGS__. Otherwise, SPACE */
#define Q(...) FIRST_ONE(dummy, ##__VA_ARGS__, SPACE)
/* SPACE just expands into two arguments so that we can differentiate between was-blank (2 args) and non-blank (1 arg) */
#define SPACE 0, 0
/* F(default, one-or-two-arguments) */
#define F(...) F_(__VA_ARGS__)
#define F_(...) F__(__VA_ARGS__)
/* delay expansion of F twice, for performing Q and SPACE */
#define F__(D, ...) H(NARGS(__VA_ARGS__), D, __VA_ARGS__)
/* H(1 or 2, default, maybe_blank) */
#define H(...) H_(__VA_ARGS__)
/* delay expansion once, for performing NARGS */
#define H_(N, D, ...) DO##N(D, __VA_ARGS__)
/* based on NARGS, pick correct result */
#define DO1(D, ...) __VA_ARGS__
#define DO2(D, ...) D

However, this ugly monstrosity is not necessary, because this answer works for me as well, and is so much cleaner. Thank you all very much!

Nero
  • 143
  • 1
  • 6