December 6th, 2021

Compiler error message metaprogramming: Helping to find the conflicting macro definition

Say you want to require that a preprocessor macro is set a particular way:

#include <contoso.h>
#if CONTOSO_VERSION != 314
#error This header file requires version 314.
#endif

Okay, if the version isn’t set correctly, you will indeed get the error, but that doesn’t help the user much with figuring out why the version number is incorrect.

You might try this:

#include <contoso.h>
#if CONTOSO_VERSION != 314
#error This header file requires version 314 \
(got CONTOSO_VERSION instead)
#endif

Unfortunately, it doesn’t work:

// MSVC
fatal error C1189: #error:  This header file requires version 314 (got CONTOSO_VERSION instead)

// gcc
error: #error This header file requires version 314 (got CONTOSO_VERSION instead)

// clang
error: This header file requires version 314 (got CONTOSO_VERSION instead)

// icc
error: #error directive: This header file requires version 314 (got CONTOSO_VERSION instead)

None of them substitute the macro in the error message, so you don’t see what version you actually got.

Here’s the trick: Just redefine the symbol.

#include <contoso.h>
static_assert(CONTOSO_VERSION == 314,
             "This header file requires version 314.");
#define CONTOSO_VERSION 314
// MSVC
error C2338: This header file requires version 314.
warning C4005: 'CONTOSO_VERSION': macro redefinition
C:\contoso\v271\contoso.h(5): note: see  previous definition of 'CONTOSO_VERSION'

// gcc
warning: "CONTOSO_VERSION" redefined
  3 | #define CONTOSO_VERSION 314

in file included from widget.h:1:
/contoso/v271/contoso.h:5: note: this is the location of the previous definition
  5 | #define CONTOSO_VERSION 271

error: static assertion failed: This header file requires version 314.
  2 | static_assert(CONTOSO_VERSION == 314,

// clang
warning: 'CONTOSO_VERSION' macro redefined [Wmacro-redefined]
#define CONTOSO_VERSION 314

/contoso/v271/contoso.h:5:9: note: previous definition is here
#define CONTOSO_VERSION 271

error: static_assert failed due to requirement '271 == 314' "This header file requires version 314."
static_assert(CONTOSO_VERSION == 314,

// icc
error: static assertion failed with "This header file requires version 314."
static_assert(CONTOSO_VERSION == 314,

warning #47: incompatible redefinition of macro "CONTOSO_VERSION" (declared at line 5 of "/contoso/v271/contoso.h")
#define CONTOSO_VERSION 314

All of the major compilers provide the courtesy of telling you where the previous conflicting definition was made. From the error message, it is evident that we are including the contoso.h header file from the v271 directory, when we presumably meant to include from the v314 directory. The thing to investigate, therefore, is the project configuration where the include directories are specified, and fix it so that the correct version of contoso.h gets included.

This trick takes advantage of the fact that the C and C++ languages both permit a macro symbol to be defined twice, provided that the second definition is identical to the first. If not, then the program is ill-formed and requires a diagnostic.

So we just redefine the macro to the value we want. If it has that value, then everything is great. If not, then a diagnostic is required. And all of the major compilers point you at the previous definition, which will help you figure out why there is a conflict.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

7 comments

Discussion is closed. Login to edit/delete existing comments.

  • SpecLad

    Seems like this won’t work correctly if CONTOSO_VERSION has an equivalent, but not equal, definition. For example, “#define CONTOSO_VERSION (300 + 14)”. The assert will pass, but the redefinition warning will still trigger.

    • Raymond ChenMicrosoft employee Author

      I’m assuming that the Contoso library itself defines its version, so you can just copy the definition from the version you want to be locked to.

  • Mystery Man

    Couldn’t we just replace the phrase “This header file” with something more descriptive?

    • Raymond ChenMicrosoft employee Author

      Sure! But I didn’t want to try to make up yet another fake name.

      • Mystery Man · Edited

        I mean, instead of doing the reassignment trick, couldn’t we just replace the phrase “This header file” with something more descriptive that helps us find the culprit? Wouldn’t the following do the trick?

        error: static assertion failed: v314/Fabrikam.h requires Contoso.h version 314.
          2 | static_assert(CONTOSO_VERSION == 314,
      • Raymond ChenMicrosoft employee Author

        The problem is that it doesn’t tell you (1) what CONTOSO_VERSION is erroneously set to, or (2) who set it to the wrong value.

      • Mystery Man

        I see. Thanks for the clarification.

        I suppose it is similar to what we experience in PowerShell, except it is the opposite. When it comes to PowerShell modules, we always have enough diagnostic information.