5

The following code has been compiled with gcc-5.4.0 with no issues:

% gcc -W -Wall a.c
...

#include <stdio.h>
#include <stdarg.h>

static int debug_flag;
static void debug(const char *fmt, ...)
{
   va_list ap;

   va_start(ap, fmt);
   vfprintf(stderr, fmt, ap);
   va_end(ap);
}

#define DEBUG(...)               \
   do {                 \
      if (debug_flag) {       \
         debug("DEBUG:"__VA_ARGS__);  \
      }              \
   } while(0)

int main(void)
{
   int dummy = 10;
   debug_flag = 1;
   DEBUG("debug msg dummy=%d\n", dummy);
   return 0;
}

However compiling this with g++ has interesting effects:

% g++ -W -Wall -std=c++11 a.c
a.c: In function ‘int main()’:
a.c:18:10: error: unable to find string literal operator ‘operator""__VA_ARGS__’ with ‘const char [8]’, ‘long unsigned int’ arguments
    debug("DEBUG: "__VA_ARGS__); \

% g++ -W -Wall -std=c++0x
<same error>

% g++ -W -Wall -std=c++03
<no errors>

Changing debug("DEBUG:"__VA_ARGS__); to debug("DEBUG:" __VA_ARGS__); i.e. space before __VA_ARGS__ enables to compile with all three -std= options.

What is the reason for such behaviour? Thanks.

Mark
  • 6,052
  • 8
  • 61
  • 129

1 Answers1

9

Since C++11 there is support for user-defined literals, which are literals, including string literals, immediately (without whitespace) followed by an identifier. A user-defined literal is considered a single preprocessor token. See https://en.cppreference.com/w/cpp/language/user_literal for details on their purpose.

Therefore "DEBUG:"__VA_ARGS__ is a single preprocessor token and it has no special meaning in a macro definition. The correct behavior is to simply place it unchanged into the macro expansion, where it then fails to compile as no user-defined literal operator for a __VA_ARG__ suffix was declared.

So GCC is correct to reject it as C++11 code.

This is one of the backwards-incompatible changes between C++03 and C++11 listed in the appendix of the C++11 standard draft N3337: https://timsong-cpp.github.io/cppwp/n3337/diff.cpp03.lex

Before C++11 the string literal (up to the closing ") would be its own preprocessor token and the following identifier a second preprocessor token, even without whitespace between them.

So GCC is also correct to accept it in C++03 mode. (-std=c++0x is the same as -std=c++11, C++0x was the placeholder name for C++11 when it was still in drafting)

It is also an incompatibility with C (in all revisions up to now) since C doesn't support user-defined literals either and considers the two parts of "DEBUG:"__VA_ARGS__ as two preprocessor tokens as well.

Therefore it is correct for GCC to accept it as C code as well (which is how the gcc command interprets .c files in contrast to g++ which treats them as C++).

To fix this add a whitespace between "DEBUG:" and __VA_ARGS__ as you suggested. That should make it compatible with all C and C++ revisions.

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
user17732522
  • 53,019
  • 2
  • 56
  • 105
  • The fix is at the end of this answer, for anyone scanning this: "To fix this add a whitespace between `"DEBUG:"` and `__VA_ARGS__` as you suggested. That should make it compatible with all C and C++ revisions." Note: [here's my `DEBUG_PRINTF()` implementation for C and C++](https://stackoverflow.com/a/67667132/4561887) – Gabriel Staples Mar 10 '23 at 20:15