7
votes

The usual way to test whether a preprocessor symbol is defined is using #ifdef. However, #ifdef cannot be used in a macro. What I need is a way to check in a macro if an argument of that macro is a defined preprocessor symbol.

For example:

#define TRACE(x,y) if(IS_DEFINED(x)){ std::cout << y; }

Here, TRACE takes two arguments, the first x should be the name of a preprocessor symbol. If such symbol is defined, the second argument should be printed. The non-existing IS_DEFINED function/macro is what I am looking for.

The usage would be as follows:

#undef BLA
TRACE(BLA,"abc") // "abc" won't be printed, as BLA is not defined
#define BLA 1
TRACE(BLA,"xyz") // "xyz" will be printed, as BLA is a defined symbol

Is there a way to achieve this? Maybe some macro magic? Of course, thes solution should work for any symbol, not only BLA or a hardcoded set of symbols. It is obviously quite easy if the set of symbols to be checked is known in advance.

5
Could you use a variable and rely on the compiler to optimize away the conditional?BonzaiThePenguin
@BonzaiThePenguin: Not sure what you mean. Example please.gexicide

5 Answers

6
votes

Compare the stringified macro (name) to the stringified (expanded) value of the macro:

#include <iostream>
#include <cstring>

#define TRACE_STRINGIFY(item) "" #item
#define TRACE(macro, message)                          \
    do {                                               \
        if (strcmp("" #macro, TRACE_STRINGIFY(macro))) \
            std::cout << message << "\n";              \
    } while (0)

The "" # macro expands to the macro name as a string, whereas TRACE_STRINGIFY(macro) first expands the macro, then stringifies the result. If the two differ, macro has to be a preprocessor macro.

This approach does fail for macros that are defined to themselves, i.e. #define FOO FOO. Such macros are not detected as preprocessor macros.

Most compilers should be able to completely optimize away the comparison of two string literals. GNU GCC (g++) 4.8.2 definitely does even with -O0 (as does gcc for C -- the same approach obviously works in C, too).

This approach does work for function-like macros, but only if you retain the parentheses (and proper number of commas, if the macro takes multiple parameters) and it is not defined to itself (e.g. #define BAR(x) BAR(x)).

For example:

#define TEST1 TEST1

#define TEST3
#define TEST4 0
#define TEST5 1
#define TEST6 "string"
#define TEST7 ""
#define TEST8 NULL
#define TEST9 TEST3
#define TEST10 TEST2
#define TEST11(x)

#define TEST13(x,y,z) (x, y, z)


int main(void)
{
    TRACE(TEST1, "TEST1 is defined");
    TRACE(TEST2, "TEST2 is defined");
    TRACE(TEST3, "TEST3 is defined");
    TRACE(TEST4, "TEST4 is defined");
    TRACE(TEST5, "TEST5 is defined");
    TRACE(TEST6, "TEST6 is defined");
    TRACE(TEST7, "TEST7 is defined");
    TRACE(TEST8, "TEST8 is defined");
    TRACE(TEST9, "TEST9 is defined");
    TRACE(TEST10, "TEST10 is defined");
    TRACE(TEST11, "TEST11 is defined");
    TRACE(TEST12, "TEST12 is defined");
    TRACE(TEST13, "TEST13 is defined");
    TRACE(TEST14, "TEST14 is defined");

    TRACE(TEST1(), "TEST1() is defined");
    TRACE(TEST2(), "TEST2() is defined");
    TRACE(TEST3(), "TEST3() is defined");
    TRACE(TEST4(), "TEST4() is defined");
    TRACE(TEST5(), "TEST5() is defined");
    TRACE(TEST6(), "TEST6() is defined");
    TRACE(TEST7(), "TEST7() is defined");
    TRACE(TEST8(), "TEST8() is defined");
    TRACE(TEST9(), "TEST9() is defined");
    TRACE(TEST10(), "TEST10() is defined");
    TRACE(TEST11(), "TEST11() is defined");
    TRACE(TEST12(), "TEST12() is defined");
    TRACE(TEST13(,,), "TEST13(,,) is defined");
    TRACE(TEST14(,,), "TEST14(,,) is defined");

    return 0;
}

which outputs

TEST3 is defined
TEST4 is defined
TEST5 is defined
TEST6 is defined
TEST7 is defined
TEST8 is defined
TEST9 is defined
TEST10 is defined
TEST3() is defined
TEST4() is defined
TEST5() is defined
TEST6() is defined
TEST7() is defined
TEST8() is defined
TEST9() is defined
TEST10() is defined
TEST11() is defined
TEST13(,,) is defined

In other words, the TEST1 symbol is not recognized as defined (because it is defined to itself), nor are TEST11 or TEST13 without parentheses. These are the limitations of this approach.

Using the parenthesized form works for all parameterless macros (except TEST1, ie. those defined to themselves), and all single-parameter macros. If a macro expects multiple parameters, you need to use the correct number of commas, as otherwise (say, if you tried TRACE(TEST13(), "...")) you get a compile-time error: "macro TEST13 requires 3 arguments, only 1 given" or similar.

Questions?

6
votes

Linux' kgconfig.h defines an __is_defined macro for this use case:

 #define __ARG_PLACEHOLDER_1 0,
 #define __take_second_arg(__ignored, val, ...) val

/*
 * Helper macros to use CONFIG_ options in C/CPP expressions. Note that
 * these only work with boolean and tristate options.
 */

/*
 * Getting something that works in C and CPP for an arg that may or may
 * not be defined is tricky.  Here, if we have "#define CONFIG_BOOGER 1"
 * we match on the placeholder define, insert the "0," for arg1 and generate
 * the triplet (0, 1, 0).  Then the last step cherry picks the 2nd arg (a one).
 * When CONFIG_BOOGER is not defined, we generate a (... 1, 0) pair, and when
 * the last step cherry picks the 2nd arg, we get a zero.
 */
#define __is_defined(x)         ___is_defined(x)
#define ___is_defined(val)      ____is_defined(__ARG_PLACEHOLDER_##val)
#define ____is_defined(arg1_or_junk)    __take_second_arg(arg1_or_junk 1, 0)

It's C99 and works for tristate options (undefined, defined to 0, defined to 1).

5
votes

Example code:

#include <iostream>

#define TRACE(name, msg) TRACE_EVAL_(TRACE_DO_, name, msg)
#define TRACE_EVAL_(macro, ...) macro(__VA_ARGS__)
#define TRACE_DO_(name, msg) \
    (#name[0] == 0 || #name[0] == '1' ? (void)(std::cout << (msg)) : (void)0)

#undef  FOO
#define BAR
#define BAZ 1

int main() {
    TRACE(FOO, "foo\n");
    TRACE(BAR, "bar\n");
    TRACE(BAZ, "baz\n");
}

Adjust the test in TRACE_DO_ as appropriate for the possible values of the macro definition. Note that supporting definitions of non-numeric values could be problematic as it would be hard to discern them from macro names...

3
votes

If you can make BLA always defined to 0 or 1 (or other value convertible to bool), you can do

#define TRACE(x, y) if (x) { std::cout << y << std::endl; }

A decent compiler will optimize the constant expression in if, so this method brings no overhead.

Update: Code for possibly not defined macros:

#define IS_DEFINED(x) IS_DEFINED2(x)
#define IS_DEFINED2(x) (#x[0] == 0 || (#x[0] >= '1' && #x[0] <= '9'))
#define TRACE(x, y) if (IS_DEFINED(x)) { std::cout << y << std::endl; }

Demo

Note that it works only if FOO is not defined, or defined to empty, or defined to some number.

How it works
For function-like macros the expansion works like this: first all macro arguments are expanded unless they are used with # or ##, then the macro itself is expanded (see formal explanation in the chapter 16.3.1 of the C++ standard, or 6.10.3.1 of the C standard).

So IS_DEFINED(x) "calls" IS_DEFINED2(x) with expanded macro x. If x is defined, it will be replaced with whatever it's defined to, otherwise it will be passed as it is.

If you call IS_DEFINED2(FOO) directly, then #x will always be equal to "FOO" which is not what you want.

2
votes

An alternative version based on Christoph's answer is one I made to allow for passing variadic parameters to the logging method (which in this case is Objective-C but should work for C++ as well).

#define TGLog(tag, msg, ...) TGLog_eval_(TGLog_do_, tag, msg, ## __VA_ARGS__)
#define TGLog_eval_(macro, ...) macro(__VA_ARGS__)
#define TGLog_do_(tag, msg, ...) \
(#tag[0] == 0 || #tag[0] == '1') ? NSLog(@"%s",[NSString stringWithFormat:msg, ## __VA_ARGS__]) : (void)0;

The ## added in front of the variadic argument variable removes the preceding comma if there are no arguments at all. This ensures the expansion does not fail due to a trailing comma.