Bug 125145 - Inconsistent behavior with concatenation of a placemarker preprocessing token allowing macro expansion when using __VA_OPT__
Summary: Inconsistent behavior with concatenation of a placemarker preprocessing token...
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: preprocessor (show other bugs)
Version: 17.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2026-05-01 23:49 UTC by Halalaluyafail3
Modified: 2026-05-01 23:53 UTC (History)
0 users

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Halalaluyafail3 2026-05-01 23:49:55 UTC
GCC awkwardly preprocesses the following code:

#define FOO()FOO
FOO()()
#define CAT1(X,Y,...)__VA_OPT__(X)##__VA_OPT__(Y)
CAT1(FOO(),,,)()
#define CAT2(X,Y,...)__VA_OPT__(X)##__VA_OPT__()
CAT2(FOO(),,,)()
#define CAT3(X,Y,...)__VA_OPT__(X)##Y
CAT3(FOO(),,,)()
#define CAT4(X,Y,...)__VA_OPT__(X)##__VA_OPT__(Y##Y)
CAT4(FOO(),,,)()
#define CAT5(X,Y,...)__VA_OPT__(X)##__VA_OPT__(Y Y)
CAT5(FOO(),,,)()

Consider the first case FOO()(), the result is clear here. FOO() gets expanded to FOO but since that identifier appeared inside of the replacement list of FOO it is not available for future macro expansion. Therefore FOO()() becomes FOO().

The second case CAT1(FOO(),,,)() is similar but after expanding FOO() it then concatenates the resulting identifier FOO with a placemarker token. GCC considers this sufficient under the "The resulting preprocessing token is available for further macro replacement" wording to allow for it to be expanded again. Therefore CAT1(FOO(),,,)() becomes FOO. The same also applies to the fifth case CAT5(FOO(),,,)().

For the cases two through for however GCC does not consider it sufficient, so CAT2(FOO(),,,)() becomes FOO(). The same also applies to CAT3(FOO(),,,)() and CAT4(FOO(),,,)().

Here is the same example but with object like macros:

#define FOO !FOO
FOO//!FOO with GCC
#define CAT1(X,Y,...)__VA_OPT__(X)##__VA_OPT__(Y)
CAT1(FOO,,,)//!!FOO with GCC
#define CAT2(X,Y,...)__VA_OPT__(X)##__VA_OPT__()
CAT2(FOO,,,)//!FOO with GCC
#define CAT3(X,Y,...)__VA_OPT__(X)##Y
CAT3(FOO,,,)//!FOO with GCC
#define CAT4(X,Y,...)__VA_OPT__(X)##__VA_OPT__(Y##Y)
CAT4(FOO,,,)//!FOO with GCC
#define CAT5(X,Y,...)__VA_OPT__(X)##__VA_OPT__(Y Y)
CAT5(FOO,,,)//!!FOO with GCC

So from examining the current behavior: concatenating with an empty argument ##E does not allow for further expansion, concatenation with ##__VA_OPT__() does not allow for further expansion, and concatenation with ##__VA_OPT__(pp-tokens) where the pp-tokens cause the hypothetical argument to be empty does allow for further expansion unless the pp-tokens contain a concatenation inside of them.

This behavior is very inconsistent, so I think the behavior should be normalized. The intended behavior as far as I understand is that concatenating with a placemarker token should not allow further macro expansions, that is it should have no effect on the other token (with two placemarkers the behavior is obvious). Clang and MSVC implement this, though EDG does the opposite.