Search code examples
c++cc-preprocessorstandardspreprocessor

C/C++: How should preprocessor directive work on macros argument list?


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?


Solution

  • 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.

    [N1570, §6.10.3 p11].

    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.