This post explains the EXAMPLE 1 given on Page# 179 (page# that would be found printed on the page of a paper-copy, not the one displayed on a pdf reader application) of the working draft, dated April 1, 2023, of the C2X standard.
Page 179, EXAMPLE 1
#define LPAREN() (
#define G(Q) 42
#define F(R, X, ...) __VA_OPT__(G R X) )
int x = F(LPAREN(), 0, <:-); // replaced by int x = 42;
The tokens for the identifiers int and x do not correspond to the names of
any macro. Hence, they are output as they are, without any change.
The token for the symbol = is not subject to replacement; it is output as it
is, without any change.
The macro-invocation is F(LPAREN(), 0, <:-). The macro F is variadic, and
its invocation here does pass a non-empty argument-list corresponding to the
trailing ... of its parameter-list. Thus, __VA_ARGS__ consists
of a single argument made up of the non-empty token-sequence <:-;
__VA_OPT__ is therefore obliged to expand the pp-tokens contained within it.
To begin the macro expansion, the CPP pushes on to the active-macro-stack-#0
an entry corresponding to this invocation of the macro F:
// active-macro-stack-#0
F(LPAREN(), 0, <:-) <---- bottom of the stack
------------------------------------------------
The argument LPAREN() corresponds to the parameter R, the argument 0
corresponds to the parameter X, and the argument <:- corresponds to the
parameter ....
Expansion of the argument LPAREN()
Since the parameter R occurs in the replacement-list of the
macro F, without being preceded by a # or a ## and without being
followed by a ##, the corresponding argument LPAREN() is subject to
argument-expansion. As it turns out, LPAREN is the name of a function-like
macro, and its invocation exists as the first argument to the macro F.
A new active-macro-stack, active-macro-stack-#1 is initialized with the entry
corresponding to the invocation LPAREN().
// active-macro-stack-#1
LPAREN() <---- bottom of the stack
-------------------------------------
The macro takes no parameters, and is provided with no arguments. Its
replacement-list is just (.
LPAREN() (rest is empty)
+-------
|
v
(
The CPP rescans the token-sequence (. The token for the symbol ( is not
subject to replacement, hence it is output as it is, without any change.
This also means that the parameter R of the macro F is now bound to the
fully-expanded argument (.
At this stage, the CPP has completed the processing of the LPAREN()
invocation. That is, the processing now will move out of the boundaries of the
replacement-list of the invocation, without any previous processing
pending. The invocation no longer influences the macro processing that lies
ahead. Its entry from the active-macro-stack-#1 is removed, and with that
removal, the stack becomes empty.
The CPP then attempts to process the rest of the tokens, but it is empty.
With that, the full expansion of the argument LPAREN() is complete.
Expansion of the argument 0
The argument 0, corresponding to the parameter X, is not an identifier;
it is not subject to the argument-expansion (IOW, its full expansion is the
same as its current form.)
Expansion of the argument for __VA_ARGS__
The argument corresponding to the parameter __VA_ARGS__ is not subject to
argument-expansion.
Expansion of the argument for __VA_OPT__
Since the argument corresponding to ... is non-empty, the parameter
__VA_OPT__ that occurs in the replacement-list of the macro must be replaced
with the expansion of its pp-tokens. This expansion is carried out with respect
to the active-macro-stack-#0 instead of creating a new stack as is usually
done for argument-expansion.
The identifier G matches the name of a function-like macro G, but within
__VA_OPT__ here, G is not followed by a ( token. Moreover, the name G
does not match with the name of any active macro (i.e. with the name of any
macro that has an entry in the active-macro-stack-#0.) Hence, this token for
the identifier G is allowed replacement during rescanning.
The identifier R represents the parameter R of the macro F. Its expansion
is (. (Note: if R were additionally a macro name, the parameter name seems
to bind more strongly than the macro name. That is, a macro parameter named R
hides a macro named R.)
The identifier X represents the parameter X of the macro F. Its expansion
is 0.
Argument Substitution
After the parameters are substituted with the corresponding, expanded arguments, the state is:
F(LPAREN(), 0, <:-) (rest is ;)
+------------------
| after arg-substitution
v---
G(0)
The state of the active-macro-stack-#0 is:
// active-macro-stack-#0
F(LPAREN(), 0, <:-) <---- bottom of the stack
------------------------------------------------
The CPP now rescans the token-sequence G(0). The identifier G is part of the
replacement-list of the macro F currently being expanded. Its token was also
not marked for non-replacement during the __VA_OPT__ processing. The
identifier also doesn’t match with the name of any active macro. Hence, this
token for the identifier G is allowed replacement during rescanning.
But G is the name of a function-like macro, and a complete invocation of that
macro exists, even without reading from the rest of the tokens, as shown
above. Hence, the invocation is processed.
The CPP pushes on to the active-macro-stack-#0 an entry corresponding to the
invocation G(0):
// active-macro-stack-#0
G(0) <---- top of the stack
F(LPAREN(), 0, <:-) <---- bottom of the stack
------------------------------------------------
Expansion of the argument 0
The argument 0, corresponding to the parameter Q, is not an identifier;
it is not subject to the argument-expansion (IOW, its full expansion is the
same as its current form.)
Argument Substitution
After the parameters are substituted with the corresponding, expanded arguments, the state is:
F(LPAREN(), 0, <:-) (rest is ;)
+------------------
| after arg-substitution
v---
G(0)
+---
| after arg-substitution
v-
42
The state of the active-macro-stack-#0 is:
// active-macro-stack-#0
G(0) <---- top of the stack
F(LPAREN(), 0, <:-) <---- bottom of the stack
------------------------------------------------
The CPP now rescans the token-sequence 42. The token for the symbol 42 is
not subject to replacement, hence it is output as it is, without any change.
At this stage, the CPP has completed the processing of the G(0) invocation.
That is, the processing now will move out of the boundaries of the
replacement-list of the invocation, without any previous processing
pending. The invocation no longer influences the macro processing that lies
ahead. Its entry from the active-macro-stack-#0 is removed.
With that removal, the CPP has also completed the processing of the
F(LPAREN(), 0, <:-) invocation. The invocation no longer influences the
macro processing that lies ahead. Its entry from the active-macro-stack-#0 is
removed, too.
The active-macro-stack-#0 is now empty:
// active-macro-stack-#0
(stack is empty)
------------------------------------------------
The CPP next moves into the rest of the tokens from the source file. The only
token left is ;, and it is not subject to replacement. It is output as it is,
without any change.
Result
The invocation F(LPAREN(), 0, <:-) expanded to a single token, 42.
Hence, the CPP outputs:
int x = 42;
Notes
The expansion of the __VA_OPT__ parameter is carried out using the same
active-macro-stack as the one on which the __VA_OPT__-containing
macro-invocation resides.