24

I have several configuration files each one containing the definition of some boolean macro, to be set to 0 or 1. Then, in my code, I check the value of such a macro to decide which part of the code to activate. Now comes the tricky part: I want to be sure that the header containing the definition of my macro has been included.

In the following example, if I forget to include the header file containing FOO definition, the compiler will print "world!", while I would like instead that it generated an error.

//in the configuration header file
#define FOO 1

//in a cpp file
#if FOO  //I would like this to generate an error if I forgot to include the header file
#pragma message "Hello"
#else
#pragma message "world!"
#endif

Is it possible to achieve such a behaviour? How?

To clarify, I am not asking how to generate an error if a macro is not defined, but if it is possible to transform the #if FOO line so that, at the same time, it checks the boolean value and generates an error if FOO is not defined.

The point of having this would be that developers would know that their code should contain

SPECIAL_MACRO(FOO)

which, at the same time, check the boolean value of FOO as if it was an #if FOO statement, and prevents them from forgetting the inclusion of the header defining FOO.

Community
  • 1
  • 1
Antonio
  • 19,451
  • 13
  • 99
  • 197
  • Since C++11 you could use `static_assert` – Paolo M Nov 17 '15 at 14:46
  • 2
    Why not use `#ifdef`? – Petr Nov 17 '15 at 14:46
  • Possible duplicate of [How to generate an error and warning in C preprocessor?](http://stackoverflow.com/questions/2221517/how-to-generate-an-error-and-warning-in-c-preprocessor) – Andrew C Nov 17 '15 at 14:50
  • @AndrewC My question is different: I know I can check if `FOO` is defined, but I would like this check to be done while checking its boolean value – Antonio Nov 17 '15 at 14:52
  • 1
    You can probably get around the whole problem by using a `constexpr bool` + templates instead of the preprocessor. – Baum mit Augen Nov 17 '15 at 14:56
  • Regarding your second edit: AFAIK, the result of a preprocessor macro expansion will never be treated as a preprocessor directive, so this is not a feasible way at least. – Baum mit Augen Nov 17 '15 at 15:05
  • @Antonio The solution is to add `#ifdef` to whatever file where you keep the special macro. Just use common sense. – Lundin Nov 17 '15 at 15:15
  • @Lundin The accepted answer is perfect, and also brings up a real case where it was used, matching exactly with my use case. I struggle to understand what makes you concerned. – Antonio Nov 17 '15 at 15:19
  • 2
    What's with the down votes? – Epic Byte Nov 19 '15 at 03:00
  • 1
    `#ifndef` and `#error` should do what you want. – Dan Korn Mar 07 '18 at 20:35
  • https://stackoverflow.com/questions/2221517/how-do-i-generate-an-error-or-warning-in-the-c-preprocessor – Dan Korn Mar 07 '18 at 20:35
  • @DanKorn I don't think you got the problem (note anyway that I am currently just aiming to reward the existing answer) – Antonio Mar 07 '18 at 23:40

5 Answers5

33

Colleagues (hi Hartmut, Kurt) who maintained a large code base which was extensively configured with #defines ran exactly into the same problem. A simple mis-spelling, possibly in a make file, could result in subtle errors which were hard to track down. Their solution: Use function macros! In

#if SOME_COND()
 // ...
#endif

the compiler complains if SOME_COND() is not defined, as opposed to a simple SOME_COND which will be replaced by 0 if undefined. I like it because it can be used to transport several values without cluttering the code up with additional #ifdefs.

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • 2
    :-) They are good programmers. When you turn on your car's OEM navigation chances are you are using their software. – Peter - Reinstate Monica Nov 17 '15 at 15:07
  • Can you show an example of how SOME_COND would be defined? – Dan Korn Mar 08 '18 at 01:06
  • @DanKorn At the highest level it contained other preprocessor function macro definitions; later, in normal source files they would comment in or out portions of code, or different variable values. – Peter - Reinstate Monica Mar 08 '18 at 06:04
  • @DanKorn I was not sure above (I worked with these guys 18 years ago); but it appears to be possible to define also function macros on the make command line, so that is what I suppose ultimately started the "macro cascade". Say, CPPFLAGS would contain `-DCAR_BRAND()=DAIMLER -DCAR_BAUREIHE()=BR220 -DCAR_FACELIFT()=2002`; then in some config.h common to all compilations more macros would be defined in ways dependent on these "master macros", those then controlling the eventual inclusion of different headers or different direct values in actual source code. – Peter - Reinstate Monica Mar 08 '18 at 06:34
  • 1
    @DanKorn Oh, maybe I misunderstood your question. `SOME_COND()` must evaluate to an integer value in an `#if` clause. I think the actual contents of such a define could be as simple as `IS_DIESEL()=1` (either per #define or per -D compiler option); or it could be an evaluation of other defines, like `HAS_FOURWD()=(MODEL()==BRxy || MODEL()== BRyz)`, where MODEL would be defined earlier, e.g. in a Makefile, but this definition would be in a central configuration header. Both (the simple and the complex) function macros could be used in an `#if` clause. – Peter - Reinstate Monica Mar 08 '18 at 11:05
  • I wonder, this generates an error, but as a side effect of parsing, which might be not reliable. Besides it requires use of `()` which is ugly. In my opinion `#error` would be more in the line with good programming practices in this case. – Serge Mar 14 '18 at 12:03
  • 1
    @Serge The OP was asking about general macro use, not only to produce errors. An error should only be raised if indeed a genuine programmer mistake happened, lik a typo. Without () the macro name could be [misspeled](https://httpd.apache.org/docs/2.4/mod/mod_speling.html) and nobody would notice, but instead the program would behave as if the macro were defined, with the value 0. – Peter - Reinstate Monica Mar 14 '18 at 12:46
  • @PeterA.Schneider yes, but `#ifndef FOO ... #error no FOO defined... #endif` would serve the same purpose, but in a controllable way, porovide clean message, and avoid `()` – Serge Mar 14 '18 at 13:51
  • 2
    @Serge You use three additional lines pus an additional level of nesting to replace a pair of (). Tastes differ, but I prefer the parentheses. Both ways seem equally "controllable" to me, whatever exactly you mean with that. – Peter - Reinstate Monica Mar 14 '18 at 14:13
  • yep, tastes are different :-) – Serge Mar 14 '18 at 14:33
7

The accepted answer of using function-macros is good, but if you want to keep normal macros - and still use the value of FOO if defined and generate an error otherwise you could do:

#if FOO / defined(FOO)
#else
#endif

If FOO is not defined it will trigger integer division by zero.

Antonio
  • 19,451
  • 13
  • 99
  • 197
Hans Olsson
  • 11,123
  • 15
  • 38
3

What about using the -Wundef gcc preprocessor option? This will only generate a warning, which can easily be turned to an error with -Werror=undef.

calandoa
  • 5,668
  • 2
  • 28
  • 25
  • Good suggestion, although there are implications in changing this project wise, and I need a cross-platform solution – Antonio Mar 14 '18 at 16:35
  • gcc will accept this flag whatever the platform. But maybe you meant "different compilers"? Many compilers are more or less compatible with gcc and may accept this option... except of course Visual Studio C/C++ which have no similar option AFAIK. – calandoa Mar 14 '18 at 16:56
  • Unfortunately Visual Studio is part of the picture – Antonio Mar 14 '18 at 17:01
2

Macro CHECK(x) will:

  • fail if macro x is undefined,
  • evaluate to 00 if x is defined to 0
  • evaluate to 01 if x is defined to 1
$ cat main.cpp
#define CAT(x, y) x##y
#define CHECK(x) CAT(0, x)

// usage

#define COND0 0
#define COND1 1

#if CHECK(COND)
#endif

#if CHECK(COND0)
#pragma message "defined 1"
#else
#pragma message "defined 0"
#endif

#if CHECK(COND1)
#pragma message "defined 1"
#else
#pragma message "defined 0"
#endif

$ g++ main.cpp
main.cpp:9:1: error: user-defined literal in preprocessor expression
    9 | #if CHECK(COND)
      | ^~~~~
main.cpp:15:17: note: ‘#pragma message: defined 0’
   15 | #pragma message "defined 0"
      |                 ^~~~~~~~~~~
main.cpp:19:17: note: ‘#pragma message: defined 1’
   19 | #pragma message "defined 1"
      |                 ^~~~~~~~~~~
pkarnakov
  • 123
  • 1
  • 7
-1

I think can solve your problem in simple tricky solution. I change your code as below and my code understand that header.h doesn't exist and show error to me.

  #if FOO == 1 
    #pragma message "Hello"
#elif FOO == 2 
    #pragma message "world!"
#else
    throw std::invalid_argument("Header didn't add to project");
#endif

only you need to change your initial value for Foo. because compiler activate Foo==0 when it doesn't find FOO, you shouldn't use 0 value for your configuration. you should leave zero for header absence situation.instead you must use values greater than zero(1 , 2, 3 , ...).

Foo==0 absence situation.

Foo==1 Configuration 1.

Foo==2 Configuration 2.

.

.

.

hamed
  • 471
  • 1
  • 9
  • 22
  • Excuse me I think my answer is repetitious. I know it late. – hamed Mar 09 '18 at 06:09
  • Can you throw syntactically in a place which would not allow statements but only declarations, e.g. at the beginning of a translation unit? – Peter - Reinstate Monica Mar 14 '18 at 05:27
  • @PeterA.Schneider I cant understand what you say. can you describe more? . I ran this code and it successfully work. – hamed Mar 14 '18 at 09:39
  • When I said "throw" I meant `throw` (the C++ keyword). My question is whether you can have a `throw` expression outside a block. An example would be if your code would be at the very beginning of a file (or more realistically, after the usual includes, but outside any function). Oh, and under the condition that `FOO` is neither 1 or 2, so that the `throw` expression is visible to the compiler ;-). I should probably simply try :-)). – Peter - Reinstate Monica Mar 14 '18 at 11:10
  • 2
    To answer my own question: Apparently it's not possible. Instead of the `throw` one could simply use the preprocessor command `#error` though. That's more in line with the idea to have a compile time error anyway. – Peter - Reinstate Monica Mar 14 '18 at 11:18