-
Notifications
You must be signed in to change notification settings - Fork 15k
Description
Summary
I have encountered a behavioral inconsistency between the ##__VA_ARGS__ preprocessor extension (supported by Clang and GCC) and the C++20 standard __VA_OPT__ feature. The issue arises when a variadic macro argument is itself a macro that expands to an empty token sequence.
It appears that ##__VA_ARGS__ determines whether to remove the preceding comma based on the presence of argument tokens before expansion, while __VA_OPT__ makes its determination based on the result after the argument has been expanded. This leads to different outcomes in a situation where I would expect identical behavior.
My expectation is that in both scenarios, the result should be the same (an empty string in my example), as the effective content of the variadic arguments is empty. I am filing this issue to ask whether this is an intended difference in behavior or a potential bug in the preprocessor implementation.
Code to Reproduce
The following code, based on the setup in my Compiler Explorer session, demonstrates the problem. Note that the macros F(...) and H(...) are defined to isolate and show the differing behaviors of ##__VA_ARGS__ and __VA_OPT__ respectively.
#define F(...) ,##__VA_ARGS__
#define H(...) __VA_OPT__(,)
#define DEFER(x) x EMPTY()
#define str(...) #__VA_ARGS__
#define SELF(x) x
#define EMPTY()
#define VA(...) __VA_ARGS__
#include<iostream>
int main() {
std::cout<<"VA_ARGS:"<<SELF(DEFER(str)(F(VA())))<<std::endl;
std::cout<<"VA_OPT:"<<SELF(DEFER(str)(H(VA())))<<std::endl;
}Link to Compiler Explorer Demonstrating the Issue
Note: The core of this issue lies in the differing expansions of
F(VA())andH(VA()). The other macros (DEFER,str,SELF, etc.) simply form a harness to capture the preprocessor's output and print it as a string for observation.
Observed Behavior
When compiled with Clang, the program produces the following output:
VA_ARGS:,
VA_OPT:
- The
##__VA_ARGS__version prints a comma because the preprocessor considersVA()to be a non-empty argument before its expansion. - The
__VA_OPT__version prints nothing (an empty string) because it evaluates the arguments after expandingVA()and correctly determines the argument list is empty.
Expected Behavior
I expect both lines to produce the same result, with the output string being empty in both cases. The output should be:
VA_ARGS:
VA_OPT:
The rationale is that the semantic intent of both ##__VA_ARGS__ and __VA_OPT__ is to handle cases where variadic arguments are absent. When an argument like VA() is passed, which has no tokens after expansion, it should be treated as an absence of arguments, and the conditional comma should be removed by both mechanisms.
Analysis and Context
As this behavior pertains to ##__VA_ARGS__, which is a non-standard extension, there is no C++ standard definition to reference. However, its purpose has largely been superseded by the standard __VA_OPT__ in C++20. The behavior of __VA_OPT__ (evaluating after expansion) feels more correct and intuitive.
The discrepancy suggests that the check for "emptiness" happens at different stages of preprocessing for these two features. This inconsistency can lead to subtle bugs and makes it difficult to reason about preprocessor behavior, especially when writing code that might rely on such extensions for compatibility with older standards. Is this divergence in logic intended, or could the behavior of the ##__VA_ARGS__ extension be considered for alignment with the more robust, standard-defined logic of __VA_OPT__?