Search code examples
cc-preprocessor

C preprocessor concatenation outside of #define


I was wondering why we can't use token concatenation outside of defines.

This comes up when I want these at the same time:

  • conflict-free naming in a library (or for "generics")
  • debugability; when using a define for this then the whole code gets merged into a line and the debugger will only show the line where the define was used

Some people might want an example (actual question is below that):

lib.inc:

#ifndef NAME
    #error includer should first define NAME
#endif
void NAME() { // works
}
// void NAME##Init() { // doesn't work
// }

main.c:

#define NAME conflictfree
#include "lib.inc"
int main(void) {
    conflictfree();
    // conflictfreeInit();
    return 0;
}

Error:

In file included from main.c:2:0:
lib.h:6:10: error: stray '##' in program
 void NAME##Init();
          ^

The rule of thumb is "concat only in define". And if I remember correctly: The reason is because of the preprocessor-phases. Question: Why does it not work. The phases-argument sounds like it was once an implementation-limitation (instead of a logical reason) and then found its way into the standard. What could be so difficult about accepting NAME##Init() if NAME() works fine?


Solution

  • In section 3.8.3.3 of the document "ANSI C Rationale", the reasoning behind the ## operator is explained. One of the basic principles states:

    A formal parameter (or normal operand) as an operand for ## is not expanded before pasting.

    This means that you would get the following:

    #define NAME foo
    
    void NAME##init();   // yields "NAMEinit", not "fooinit"
    

    This makes it rather useless in this context, and explains why you have to use two layers of macro to concatenate something stored in a macro. Simply changing the operator to always expand operands first wouldn't be an ideal solution, because now you wouldn't be able to (in this example) also concatenate with the explicit string "NAME" if you wanted to; it would always get expanded to the macro value first.