Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this a valid way of checking if a variadic macro argument list is empty?

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:

  • This creates a compound literal char array and initializes it by using a string literal.
  • No matter what is passed to the macro, all arguments will be translated to one long string literal.
  • In case the macro list is empty, the string literal will become "", which consists only of a null terminator and therefore has size 1.
  • In all other cases, it will have a size greater than 1.
like image 582
Lundin Avatar asked Mar 29 '19 12:03

Lundin


People also ask

Is printf Variadic function?

The C printf() function is implemented as a variadic function.

What is #__ Va_args __?

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.


2 Answers

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.

Variadic macros and their variable arguments

[Controversial, much disputed position removed. It was more distracting than helpful.]


The proposed macro

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,

  • lexically, as a preprocessing token, sizeof is categorized as an identifier (there is no distinction between keywords and identifiers for preprocessing tokens), and
  • according 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.

like image 184
John Bollinger Avatar answered Oct 11 '22 00:10

John Bollinger


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

like image 1
silmaril Avatar answered Oct 10 '22 23:10

silmaril