5

I have a project where there are two different preprocessor macros with the same name, defined in two different include files (from two different libraries), and I have to check if they have the same value at build time.

So far I could make this check at run time, assigning the macro values to different variables in different implementation files, each including only one of the headers involved.

How can I do it at build time?

This is what I tried so far (where Macro1.h and Macro2.h are third-party files I cannot modify):

Header files:

TestMultiMacros.h:

#ifndef TEST_MULTI_MACROS_H
#define TEST_MULTI_MACROS_H

struct Values
{
    static const unsigned int val1, val2;
    static const unsigned int c1 = 123, c2 = 123;
};

#endif // TEST_MULTI_MACROS_H

Macro1.h:

#ifndef MACRO1_H
#define MACRO1_H

#define MY_MACRO 123

#endif // MACRO1_H

Macro2.h:

#ifndef MACRO2_H
#define MACRO2_H

#define MY_MACRO 123

#endif // MACRO2_H

Implementation files:

TestMultiMacros1.cpp:

#include "TestMultiMacros.h"
#include "Macro1.h"

const unsigned int Values::val1 = MY_MACRO;

TestMultiMacros2.cpp:

#include "TestMultiMacros.h"
#include "Macro2.h"

const unsigned int Values::val2 = MY_MACRO;

entrypoint.cpp:

#include "TestMultiMacros.h"

using namespace std;

static_assert(Values::val1 == Values::val2, "OK");  // error: expression did not evaluate to a constant
static_assert(Values::c1 == Values::c2, "OK");

int main()
{
}

I would be interested in a solution using both C++11 and C++17.

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
Pietro
  • 12,086
  • 26
  • 100
  • 193

2 Answers2

9

Include the first header. Then save the value of the macro to a constexpr variable:

constexpr auto foo = MY_MACRO;

Then include the second header. It should silently override MY_MACRO. If your compiler starts complaining, do #undef MY_MACRO first.

Then compare the new value of the macro with the variable using a static_assert:

static_assert(foo == MY_MACRO, "whatever");
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 3
    *"It should silently override MY_MACRO. If your compiler starts complaining, do #undef MY_MACRO first."* I say that you should always undef the first macro. If second header doesn't for some reason have the macro defined, static assertion should fail to compile. – user694733 Feb 13 '20 at 10:58
  • I tried with both `constexpr` and `const`, and I get this error: "static assertion failed with "whatever"" – Pietro Feb 13 '20 at 10:59
  • 2
    @Pietro It means the values are different. Try printing `foo` and `MY_MACRO`. – HolyBlackCat Feb 13 '20 at 11:01
  • Sadly that doesn't work if the types are incompatible, but of course then it still fails to compile (which is mission accomplished, in a sense) – stefan Feb 13 '20 at 11:02
  • @Pietro What is your compiler and C++ version? How are the values defined? – VLL Feb 13 '20 at 11:05
  • @HolyBlackCat - There is no other way apart than undefining the macro, isn't it? – Pietro Feb 13 '20 at 11:07
  • @vll - I am using Visual C++ 2013 and 2019, but I did not specify it because I prefer a compiler agnostic answer. – Pietro Feb 13 '20 at 11:11
  • 2
    @Pietro This answer seems to work with Visual C++, therefore if the assertion fails for you the values are different: https://godbolt.org/z/p7mqQA – VLL Feb 13 '20 at 11:15
1

Here's a very simple C++17 test which works with arbitrary (non-function) macros by comparing the text of the macro expansion. For c++11, which lacks the constexpr comparison in std::string_view, you can write it yourself in a couple of lines, as shown in this answer.

#include <string_view>
#define STRINGIFY(x) STRINGIFY_(x)
#define STRINGIFY_(x) #x

#include "macro1.h"
//#define MY_MACRO A night to remember
constexpr const char* a = STRINGIFY(MY_MACRO);

#undef MY_MACRO
#include "macro2.h"
//#define MY_MACRO A knight to remember
constexpr const char* b = STRINGIFY(MY_MACRO);     

static_assert(std::string_view(a) == b, "Macros differ");

int main() { }

(Godbolt: https://godbolt.org/z/nH5qVo)

Of course, this depends on what exactly you mean by equality of macros. This version will report failure if one header file has

#define MY_MACRO (2+2)

and the other has

#define MY_MACRO 4

Also worth noting that stringification normalises whitespace but it does not normalise the presence of whitespace other than trimming the ends. So (2 + 2) and (2 + 2) will compare as equal, but not (2+2) and ( 2 + 2 )

rici
  • 234,347
  • 28
  • 237
  • 341