Issue 1069: Placemarker preprocessing token concatenation and expansion

Authors: Jay Ghiron
Date: 2026-05-28
Submitted against: C23
Status: Open

Implementations disagree about the meaning of the following text, specifically when concatenating a placemarker preprocessing token with an identifier that was disabled from being expanded:

For both object-like and function-like macro invocations, before the replacement list is reexamined for more macro names to replace, each instance of a ## preprocessing token in the replacement list (not from an argument) is deleted and the preceding preprocessing token is concatenated with the following preprocessing token. Placemarker preprocessing tokens are handled specially: concatenation of two placemarkers results in a single placemarker preprocessing token, and concatenation of a placemarker with a non-placemarker preprocessing token results in the non-placemarker preprocessing token. If the result is not a valid preprocessing token, the behavior is undefined. The resulting token is available for further macro replacement. The order of evaluation of ## operators is unspecified.

(C23 6.10.5.4 "The ## operator" paragraph 3.)

Consider the following macros:

#define FOO()FOO
#define CAT(X,Y,...)__VA_OPT__(X)##__VA_OPT__(Y)

FOO() will expand to FOO, but the resulting identifier cannot be expanded again. Therefore FOO()() will expand to FOO() rather than FOO. CAT(FOO(),,,)() does the same as FOO()(), but it concatenates the resulting identifier with a placemarker preprocessing token before attempting to expand it again. If "The resulting token is available for further macro replacement." is interpreted as applying to this concatenation, then it should expand to FOO. Otherwise, it should expand to FOO(). GCC and EDG expand it to FOO, but Clang and MSVC expand it to FOO(). The interpretation of this wording by GCC is not consistent however, see also GCC issue 125145.

I believe the intent here is that concatenating with a placemarker preprocessing should be effectless, meaning that the result of CAT(FOO(),,,)() should be FOO(). It would be useful to add wording to clarify what the result here is.

Suggested correction

Modify C23 6.10.5.4 paragraph 3:

The resulting token is available for further macro replacement if neither of the two concatenated tokens are placemarker preprocessing tokens.