53

Using C++ preprocessor directives, is it possible to test if a preprocessor symbol has been defined but has no value? Something like that:

#define MYVARIABLE
#if !defined(MYVARIABLE) || #MYVARIABLE == ""
... blablabla ...
#endif

EDIT: The reason why I am doing it is because the project I'm working on is supposed to take a string from the environment through /DMYSTR=$(MYENVSTR), and this string might be empty. I want to make sure that the project fails to compile if user forgot to define this string.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
galets
  • 17,802
  • 19
  • 72
  • 101

11 Answers11

51

Soma macro magic:

#define DO_EXPAND(VAL)  VAL ## 1
#define EXPAND(VAL)     DO_EXPAND(VAL)

#if !defined(MYVARIABLE) || (EXPAND(MYVARIABLE) == 1)

Only here if MYVARIABLE is not defined
OR MYVARIABLE is the empty string

#endif

Note if you define MYVARIABLE on the command line the default value is 1:

g++ -DMYVARIABLE <file>

Here the value of MYVARIABLE is the empty string:

g++ -DMYVARIABLE= <file>

The quoting problem solved:

#define DO_QUOTE(X)        #X
#define QUOTE(X)           DO_QUOTE(X)

#define MY_QUOTED_VAR      QUOTE(MYVARIABLE)

std::string x = MY_QUOTED_VAR;
std::string p = QUOTE(MYVARIABLE);
Chnossos
  • 9,971
  • 4
  • 28
  • 40
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • Hm, that breaks with `#define A "a"`. – Georg Fritzsche Sep 23 '10 at 23:04
  • As the pre-processor can add quotes auto-magically but can not take them away. It is unusual to see quotes in macros. So it is quite easy to get around this limitation. – Martin York Sep 24 '10 at 01:07
  • 4
    But how would you handle `-DX="#"` or `-DY="a b"`? – Georg Fritzsche Sep 24 '10 at 01:38
  • 3
    This is quite clever, but it will break the build when `MYVARIABLE` is actually defined, since `EXPAND(MYVARIABLE)` will return non-integer value and compiler will stop at `#if !defined(MYVARIABLE) || (EXPAND(MYVARIABLE) == 1)` – galets Sep 24 '10 at 16:35
  • @galets: Any identifier in a preprocessor condition is replaced as if it is a macro. If it is not defined then it returns 0. So the above code works fine as long as MYVARIABLE is EMPTY or MYVARIABLE is a valid identifier. – Martin York Sep 24 '10 at 21:14
  • @galets: Try: `#define MYVARIABLE Bob` for example works fine. EXPAND(MYVARIABLE) will return Bob1 which the preprocessor will replace with 0 as Bob1 is an undefined MACRO this will then fail the test 0 == 1 and thus the #if will not be expanded. – Martin York Sep 24 '10 at 21:15
  • @Martin York: I think I know what my problem is. The string I tested with ends with "!", so this code fails to compile: #define MYVARIABLE Test! #if !defined(MYVARIABLE) || (EXPAND(MYVARIABLE) == 1) #endif with: fatal error C1012: unmatched parenthesis : missing ')' – galets Sep 24 '10 at 22:08
  • @galets: As does anything with a space, `||`, `#`, ... in it. – Georg Fritzsche Sep 24 '10 at 23:13
  • 6
    Or more precisely if it contains anything other than "[A-Za-z_][A-Za-z0-9_]*" The above will fail – Martin York Sep 24 '10 at 23:21
  • @mchiasson: Hate to argue but it does. You must be doing somthign wrong. – Martin York Oct 24 '14 at 21:12
  • 4
    **DANGER**: It does not work with `-DMYVARIABLE=abcd` if you have a `#define abcd 0` somewhere above your `#if` (because then `EXPAND(MYVARIABLE)` becomes `01` which **is** equal to `1`). If `MYVARIABLE` is to hold a user defined filename, you can never know whether the filename is actually the name of a macro defined to `0` (or `0x`, or `00`, ...) and the build will fail. – not-a-user Jan 08 '15 at 16:02
  • 3
    A slight improvement over Loki's solution is `#define NO_OTHER_MACRO_STARTS_WITH_THIS_NAME_`, `#define IS_EMPTY(name) defined(NO_OTHER_MACRO_STARTS_WITH_THIS_NAME_ ## name)`, `#define EMPTY(name) IS_EMPTY(name)`, `#if !defined(MYVARIABLE) || EMPTY(MYVARIABLE)`. This additionally works if `MYVARIABLE` is `0`, `0x`, or `00`, etc. (or the name of a macro defined to one of these). – not-a-user Jan 08 '15 at 16:18
  • 1
    There are a number of "gotchas", already described in the comments, which you should warn about in your answer, to save the unwary from trouble. – Craig McQueen Sep 22 '15 at 01:53
  • It doesn't seem to work in Visual Studio 2013. Claiming there's a missing '. Do you have a workaround? – Barney Szabolcs Oct 27 '15 at 19:31
  • I came here looking for solutions to a similar problem. I wanted to test for the presence of the UNICODE preprocessor symbol within a macro. After a bit of tinkering and though, I concluded that this was a case in which a small inline function made more sense. – David A. Gray Aug 03 '19 at 02:57
19

I haven't seen this solution to the problem but am surprised it is not in common use . It seems to work in Xcode Objc. Distinguish between "defined with no value" and "defined set 0"

#define TRACE
#if defined(TRACE) && (7-TRACE-7 == 14)
#error TRACE is defined with no value
#endif
Starx
  • 77,474
  • 47
  • 185
  • 261
Ian Brackenbury
  • 199
  • 1
  • 2
12

I want to make sure that the project fails to compile if user forgot to define this string.

While i'd check this in a previous build-step, you can do this at compile-time. Using Boost for brevity:

#define A "a"
#define B
BOOST_STATIC_ASSERT(sizeof(BOOST_STRINGIZE(A)) > 1); // succeeds
BOOST_STATIC_ASSERT(sizeof(BOOST_STRINGIZE(B)) > 1); // fails
Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
  • I think that is the answer. Making something fail on an undefined preprocessor macro sounds like an opposite of many questions here on SO. – Dummy00001 Sep 23 '10 at 23:21
10

Mehrad's answer must be expanded to make it work. Also his comment

/* MYVARI(A)BLE is undefined here */

is not correct; to test for an undefined variable, there is the simple test #ifndef MYVARIABLE.

After such test however, his expression leads to a correct solution of the original question. I tested that this code works, for undefined, defined but empty, and non-empty values of the macro MYVARIABLE:

#ifndef MYVARIABLE
    /* MYVARIABLE is undefined here */
#elif ~(~MYVARIABLE + 0) == 0 && ~(~MYVARIABLE + 1) == 1
    /* MYVARIABLE is defined with no value here */
#else
    /* MYVARIABLE is defined here */
#endif

The #elif statement ~(~MYVARIABLE + 0) == 0 && ~(~MYVARIABLE + 1) == 1 works as follows :

  • When MYVARIABLE is defined but empty, it expands to ~(~+0) == 0 && ~(~+1) == 1, which works out 0==0 && 1==1 (the double negation ~~ being an identity operator).
  • When MYVARIABLE is defined to a numeric value, say n, it expands to ~(~n+0)==0 && ~(~n+1)==1. On the left hand side of &&, the expression ~(~n+0)==0 evaluates to n==0. But with n==0, the right hand side evaluates to ~(~0+1)==1, with ~0 being -1 to ~(-1+1)==1, then ~0==1 and finally -1==1, which obviously is false.
  • When MYVARIABLE is defined to a non-numeric value, the precompiler reduces all unknown symbols to 0, and we get the previous case with n==0 once more.

My complete test code (save as file test.c) :

#include <stdio.h>

int main() {
    printf("MYVARIABLE is "
#ifndef MYVARIABLE
     "undefined"
#elif ~(~MYVARIABLE + 0) == 0 && ~(~MYVARIABLE + 1) == 1
     "defined without a value"
#else 
     "defined with this value : %i", MYVARIABLE
#endif
    );
    printf("\n");
}

With the GNU preprocessor cpp you can experiment to see what code is produced:

# undefined
cpp test.c
#defined without a value
cpp -DMYVARIABLE= test.c
#defined wit an implicit value 1
cpp -DMYVARIABLE test.c
#defined wit an explicit value 1
cpp -DMYVARIABLE=1 test.c
#defined wit an explicit value a
cpp -DMYVARIABLE=a test.c

or output of compilation and execution (under some linux)

$ gcc -o test test.c ; ./test
MYVARIABLE is undefined
$ gcc -DMYVARIABLE= -o test test.c ; ./test
MYVARIABLE is defined without a value
$ gcc -DMYVARIABLE -o test test.c ; ./test
MYVARIABLE is defined with this value : 1
$ gcc -DMYVARIABLE=1 -o test test.c ; ./test
MYVARIABLE is defined with this value : 1
$ gcc -DMYVARIABLE=a -o test test.c ; ./test
test.c: In function ‘main’:
<command-line>:0:12: error: ‘a’ undeclared (first use in this function)
...

In the last run, where MYVARIABLE is defined as 'a', the error is not an error in the macro definition; the macro is correctly lead to the last case, "defined with this value...". But this value being 'a', and 'a' not being defined in the code, the compiler or course has to signal this.

In that way, the last case is a very good example of why the intent of the original question is very dangerous: via a macro the user can introduce any sequence of program lines in the code to be compiled. Checking that such code is not introduced, requires a lot more checking of the macro on valid values. Probably a full script is needed, instead of leaving this task to preprocessing. And in that case, what is the use of checking it in preprocessing too?

stm
  • 662
  • 1
  • 6
  • 23
db-inf
  • 109
  • 1
  • 3
  • This is a pretty cool trick, but I'm not understanding the purpose of the LHS of the #elif `~(~MYVARIABLE + 0) == 0` , wouldn't it be sufficient to simply have the RHS? – skaravos Apr 12 '22 at 01:49
  • The second case of the detailed explanation of that statement proves the necessity of both sides. – db-inf Apr 13 '22 at 07:34
3

You can use the BOOST_PP_IS_EMPTY macro like so:

#include <boost/preprocessor/facilities/is_empty.hpp>

#define MYVARIABLE
#if !defined(MYVARIABLE) || !BOOST_PP_IS_EMPTY(MYVARIABLE)
    // ... blablabla ...
#endif

That did the trick for me. I shall add this macro is undocumented, so use it with caution.

Source: preprocessor-missing-IS-EMPTY-documentation

mister why
  • 1,967
  • 11
  • 33
3

You can't since the preprocessor can only check for a numeric value. Your string compare is not covered by preprocessor syntax.

harper
  • 13,345
  • 8
  • 56
  • 105
2

I don't think that this can be done. That being said, I don't see a need for it. When you make a preprocessor #define symbol, you should establish a convention that either you define it as 1 or 0 for use in #if, or you leave it blank.

Reinderien
  • 11,755
  • 5
  • 49
  • 77
  • "You should establish a convention" .. unfortunately, that's not always possible. Take, for example, '__LITTLE_ENDIAN__'. It's defined by some compilers, and not by others. Most of the ones I've seen that define it define it as 0 or 1. So, you can't just say "'#if __LITTLE_ENDIAN__'" :( (And, ifdef doesn't solve the problem for you.) Sigh, can't figure out how to get the two underscores before/after LITTLE_ENDIAN. Even used backtick to quote as code. – Stan Sieler Oct 13 '21 at 23:57
2

For integer-only macros...

You can use a hack with no extra macros:

#if ~(~MYVARIABLE + 0) == 0 && ~(~MYVARIABLE + 1) == 1
/* MYVARIBLE is undefined here */
#endif
Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
0

#if MYVARIABLE==0 My answer must be at least 30 chars, so that should do it!

Kris Morness
  • 574
  • 5
  • 12
0

On a related note, often we want to check if a macro is undefined, is zero, or has an integer value other than zero:

#if ! defined(MYVARIABLE)
  // UNDEFINED:  MYVARIABLE is undefined
#elif ~ MYVARIABLE + 1
  // TRUE:       MYVARIABLE is defined and is empty or nonzero integer
#else
  // FALSE:      MYVARIABLE is defined and is zero
#endif

How does this work?

  • if MYVARIABLE is empty then ~ + 1 evaluates to nonzero (true)
  • if MYVARIABLE is nonzero (for example 1) then ~ 1 + 1 evaluates to nonzero (true)
  • if MYVARIABLE is zero then ~ 0 + 1 evaluates to zero (false)

This method works for any integer value for MYVARIABLE. When MYVARIABLE is the name of another macro, that macro's value will be used, as should be expected.

Note that if you just want to check if MYVARIABLE as a compile-time flag is enabled (defined and empty or nonzero):

#if defined(MYVARIABLE) && ~ MYVARIABLE + 1
  // the MYVARIABLE feature is enabled
#endif
Dr. Alex RE
  • 1,772
  • 1
  • 15
  • 23
  • Unfortunately, that breaks if MYVARIABLE is a string. All this discussion points out a fundamental flaw in C/C++: the inability to determine if a symbol is defined with a value. – Stan Sieler Oct 14 '21 at 00:01
0

Warning! Dirty hack:

int xxx[1/(sizeof(PASSWORD)-1)];

Will blow up if password wasn't defined (in my case, aquired from an environmental variable that needs to be externally defined)

Brad
  • 11,262
  • 8
  • 55
  • 74