I've been looking for a way to check if a variadic macro argument list is empty. All solutions I find seem to be either quite complex or using non-standard extensions.
I think I've found an easy solution that is both compact and standard:
#define is_empty(...) ( sizeof( (char[]){#__VA_ARGS__} ) == 1 )
Q: Are there any circumstances where my solution will fail or invoke poorly-defined behavior?
Based on C17 6.10.3.2/2 (the # operator): "The character string literal corresponding to an empty argument is ""
", I believe that #__VA_ARGS__
is always well-defined.
Explanation of the macro:
""
, which consists only of a null terminator and therefore has size 1. The C printf() function is implemented as a variadic function.
16.3.An identifier __VA_ARGS__ that occurs in the replacement list shall be treated as if it were a parameter, and the variable arguments shall form the preprocessing tokens used to replace it. this is the same for varags as it is for normal parameters.
Note: this version of this answer is the result of a major rewrite. Some claims have been removed and others significantly modified, so as to focus on and better justify the most important points.
[Controversial, much disputed position removed. It was more distracting than helpful.]
I think I've found an easy solution that is both compact and standard:
#define is_empty(...) ( sizeof( (char[]){#__VA_ARGS__} ) == 1 )
We can sidestep any question of undefinedness by considering this variation:
#define is_empty(dummy, ...) ( sizeof( (char[]){#__VA_ARGS__} ) == 1 )
. The same considerations apply to the interpretation of empty vs. non-empty variable arguments here as do in your original version. Specifically,
Based on C17 6.10.3.2/2 (the # operator): "The character string literal corresponding to an empty argument is """, I believe that
#__VA_ARGS__
is always well-defined.
I agree. Also relevant here is section 6.10.3.1/2: "An identifier __VA_ARGS__
that occurs in the replacement list shall be treated as if it were a parameter [...]."
Explanation of the macro:
- This creates a compound literal char array and initializes it by using a string literal.
Yes.
- No matter what is passed to the macro, all arguments will be translated to one long string literal.
Yes. __VA_ARGS__
is treated as a (one) parameter. If there are multiple variable arguments then that can impact the rescan, but the stringification operator has its effect at the point of the macro expansion, before rescanning.
- In case the macro list is empty, the string literal will become "", which consists only of a null terminator and therefore has size 1.
Yes.
- In all other cases, it will have a size greater than 1.
Yes. This holds even in the case of two zero-token arguments in the variable argument list, is_empty(dummy,,)
, where #__VA_ARGS__
will expand to ","
. It also holds in the case of an argument consisting of an empty string literal, is_empty(dummy, "")
, where #__VA_ARGS__
will expand to "\"\""
.
HOWEVER, that still might not serve your purpose. In particular, you cannot use it in a conditional compilation directive. Although sizeof
expressions are generally allowed in integer constant expressions, such as form the control expressions of such directives,
sizeof
is categorized as an identifier (there is no distinction between keywords and identifiers for preprocessing tokens), andaccording to paragraph 6.10.1/4 of the standard, when processing the control expression of a conditional compilation directive,
After all replacements due to macro expansion and the defined unary operator have been performed, all remaining identifiers (including those lexically identical to keywords) are replaced with the pp-number 0
(emphasis added).
Therefore, if your macro is used as or in the control expression of a conditional compilation directive then it will be evaluated as if the sizeof
operator in it were replaced by 0
, yielding an invalid expression.
Personnally i don't like mixing macro/preprocessor-level evaluation and compilation-level test.
There seem to be no standard way to do it at the macro level, but hacks exists here: C++ preprocessor __VA_ARGS__ number of arguments
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With