https://gcc.gnu.org/bugzilla/show_bug.cgi?id=123325
Bug ID: 123325
Summary: incorrect __VA_OPT__ handling
Product: gcc
Version: 16.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: preprocessor
Assignee: unassigned at gcc dot gnu.org
Reporter: viro at zeniv dot linux.org.uk
Target Milestone: ---
__VA_OPT__ handling in gcc preprocessor (both for C and C++) violates
6.10.5.1[7] (C23) and its C++ counterparts (15.6.1[4] C++20, 15.6.2[4] C++23).
All of those define the result of argument substitution for va-opt-replacement
in case when __VA_ARGS__ substitution would result in no tokens to be a
placeholder, with the only constraint being that the sequence of pp-tokens in
it must be well-formed. Consider
#define A(X) X
#define B A(
#define Z(X,...) __VA_OPT__(X)
Z(B)
In Z(B) __VA_ARGS__ is clearly empty. va-opt-replacement is __VA_OPT__ ( X ),
and the sequence of tokens in it is obviously well-formed, so all constraints
are satisfied. In other words, Z(B) ought to expand to an empty sequence of
tokens.
What happens instead is that gcc decides to expand the first argument of Z,
even
though standard does not demand such expansion. That would be fine (result of
expansion is discarded), but in this case the unasked for expansion of argument
results in an error and preprocessing fails with
<stdin>:4:4: error: unterminated argument list invoking macro ‘A’
AFAICS, that happens at least for 14.2.0 (debian/x86), 15.2.1 (fedora/x86) as
well as trunk (commit a74ef4bd7b6e, Dec 28 2025).
Note that gcc would (correctly) accept
#define A(X) X
#define B A(
#define Z(X,...)
Z(B)
without an attempt to expand the argument of Z. Apparently the logics for
deciding whether an argument needs to be expanded treats va-opt-replacement the
same way it treats other parts of replacement list. Unfortunately, the rules
for va-opt-replacement are not the same - for the purposes of argument
substitution the entire va-opt-replacement is treated as a single parameter
[6.10.5.1[4]] and argument substitution of anything inside it happens only if
__VA_ARGS__ would expand to something non-empty [6.10.5.1[7]].
Reproducer:
cpp <<'EOF'
#define A(X) X
#define B A(
#define Z(X,...) __VA_OPT__(X)
Z(B)
EOF