Issue 162609
Summary Discrepancy in preprocessor behavior between `##__VA_ARGS__` and `__VA_OPT__` when variadic argument is a macro that expands to nothing
Labels new issue
Assignees
Reporter lhish
    ### **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.

```cpp
#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](https://godbolt.org/z/fq88v7joP)**

> **Note:** The core of this issue lies in the differing expansions of `F(VA())` and `H(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 considers `VA()` to be a non-empty argument before its expansion.
- The `__VA_OPT__` version prints nothing (an empty string) because it evaluates the arguments *after* expanding `VA()` 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__`?
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to