How to test if preprocessor symbol is #define'

2019-01-14 11:40发布

问题:

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.

回答1:

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 1

g++ -DMYVARIABLE= <file>

Here the value of MYVARIABLE is the empty string

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);


回答2:

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


回答3:

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


回答4:

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.



回答5:

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



回答6:

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



回答7:

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


回答8:

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
    /* MYVARIBLE is defined with no value here */
#else
    /* MYVARIBLE 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 -DMYVARIALBE= test.c
#defined wit an implicit value 1
cpp -DMYVARIALBE test.c
#defined wit an explicit value 1
cpp -DMYVARIALBE=1 test.c
#defined wit an explicit value a
cpp -DMYVARIALBE=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?



回答9:

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