Both C and C++ standards specify the following:
16.3.1 Argument substitution (C++11)
6.10.3.1 Argument substitution (C11)
After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.
One can interpret this paragraph as if the standards require to:
(1) At first identify macro arguments (comma separated) and then expand all the macros contained in each argument separately,
or
(2) Expand all the macros contained in the argument list and then identify each argument.
To illustrate this, let's consider this sample code:
#define CONDITION (0)
#if (CONDITION > 0)
#define FunctionAlias(par_a, par_b, par_opt, par_c) \
FunctionName(par_a, par_b, par_opt, par_c)
#else
#define FunctionAlias(par_a, par_b, par_c) \
FunctionName(par_a, par_b, par_c)
#endif
int global_a, global_b, global_c;
#if (CONDITION > 0)
int global_opt;
#endif
void FunctionName(int a, int b, int c)
{
}
void AnotherFunction()
{
FunctionAlias(
global_a,
global_b,
#if (CONDITION > 0)
global_opt,
#endif
global_c
);
}
(1) Approach one would produce invalid code:
int global_a, global_b, global_c;
void FunctionName(int a, int b, int c)
{
}
void AnotherFunction()
{
FunctionName(global_a, global_b, #if ((0) > 0) global_opt);
}
(2) Approach 2 produces valid C code:
int global_a, global_b, global_c;
void FunctionName(int a, int b, int c)
{
}
void AnotherFunction()
{
FunctionName(global_a, global_b, global_c);
}
Which of the interpretations of the standards is the correct one?
First, you cannot, at all, put preprocessing directives inside the arguments to a function-like macro, because of some text a little down from what you quoted:
If there are sequences of preprocessing tokens within the list of arguments that would otherwise act as preprocessing directives, the behavior is undefined.
Second, independent of that, the standard requires the behavior you called (1). This is specified by this part of the text you quoted:
Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.
This sentence wouldn't make any sense if the arguments to a function-like macro were expanded before the boundaries between arguments were identified. You can also see this by experiment, with a slight modification of your code:
#if (CONDITION > 0)
#define FunctionAlias(par_a, par_b, par_opt, par_c) \
FunctionName(par_a, par_b, par_opt, par_c)
#else
#define FunctionAlias(par_a, par_b, par_c) \
FunctionName(par_a, par_b, par_c)
#endif
int global_a, global_b, global_c;
#if (CONDITION > 0)
int global_opt;
#define GLOBAL_OPT global_opt,
#else
#define GLOBAL_OPT /*nothing*/
#endif
void FunctionName(int a, int b,
#if CONDITION > 0
int opt,
#endif
int c)
{
}
void AnotherFunction()
{
FunctionAlias(
global_a,
global_b,
GLOBAL_OPT
global_c
);
}
This will compile fine if CONDITION
is not defined or zero, but when CONDITION
is nonzero, you will get an error along the lines of
test.c: In function ‘AnotherFunction’:
test.c:28:17: error: macro "FunctionAlias" requires 4 arguments, but only 3 given
28 | );
| ^
demonstrating that GLOBAL_OPT
was not expanded before looking for the four arguments to FunctionAlias
.