11

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.

2
  • 1
    Could you use a variable and rely on the compiler to optimize away the conditional? Commented Sep 29, 2014 at 12:27
  • @BonzaiThePenguin: Not sure what you mean. Example please. Commented Sep 29, 2014 at 12:28

7 Answers 7

12

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, i.e., 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.

Sign up to request clarification or add additional context in comments.

3 Comments

Interesting, but how do you know that comparison of string literals is optimized away at compile time? (some hard reference would be good, assembler inspection is anecdotal)
Well, so the best reference I could find is that the GCC manual page on builtins doesn't say anything really concrete. But still, GCC 5.2 -O0 and Clang 3.8 -O1 both do evaluate the strcmp at compiletime. This is great for embedded development! I wrote a blog post about using this in plain C in a more generic way: hmijailblog.blogspot.com/2016/03/…
@hmijail: I like the __builtin_strcmp() approach in your blog post; it has the highest likelihood of being evaluated at compile time. In general, the lack of string comparisons in cpp (the C preprocessor) is annoying, and kind of surprising, too. However, if we were to restrict ourselves to numeric trigger/rule macros, everything is much, much simpler.. I especially like the way GNU C library uses #define _POSIX_C_SOURCE YYYYMML to control which version of POSIX C features are exported. (The L is to make it always a long.)
7

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

1 Comment

This only works for boolean macros though. It fails for macros that define strings, integers, or reference other macros. #define EXAMPLE_MACRO_NO_VALUE #define EXAMPLE_MACRO_VALUE_IS_NAME EXAMPLE_MACRO_VALUE_IS_NAME
5

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...

1 Comment

Only works for #define FOO <numbers>. For example, won't work for #define FOO yes. Nominal Animal got it right.
3

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.

6 Comments

Yes, this is simple. This is also what I am currently doing. However, it is quite inconvenient. Having a solution that works for undefined symbols would make things much easier.
Wow, that looks nice. But how does it work? And why do we need another IS_DEFINED2? I know it looks like stringify, but still I am unable to decipher it completely :D.
@gexicide I added explanation to the answer
Why construct like #x[0]?
@zoska it tests the first character in string representation of x.
|
2

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.

Comments

2

Most of the other solutions on this page are not pure preprocessor solutions – at least part of the test occurs in C (or C++ or Objective-C) code, not in the preprocessor. While that may be acceptable for some use cases, it is a big limitation for others, especially if you are trying to write a macro which generates code (declarations, statements, etc.) only if some macro is defined.

The Linux kernel __is_defined macro is a pure preprocessor solution, but it only works for Boolean macros (-DENABLE_FOO=1). It cannot test whether a macro is defined at all (-DENABLE_FOO).

Using Paul Fultz's cloak.h, we can create a solution which is pure preprocessor, yet works for a wide variety of scenarios:

#include "cloak.h"
#define WHEN_DEFINED(token,then) \
   WHEN(NOT_EQUAL(DEFINED__##token,CAT(DEFINED__,token)))(then)

Basically, we are pasting DEFINED__ with the token twice, one time doing macro expansion, the other time without, and comparing them. If the macro expanded to something else, they will be different, in which case we expand the then clause.

In order for this to work, we need a helper macro for each macro we want to test for; for example, to test for TEST1, we need:

#define COMPARE_DEFINED__TEST1(x) x

Now we can do:

WHEN_DEFINED(TEST1,only_called_if_TEST1_defined());

This approach works if TEST1 is defined to be any of the following:

  • empty (#define TEST1)
  • any non-negative integer (#define TEST1 0)
  • an identifier other than itself (#define TEST1 TEST2)

It will not work if TEST1 is defined to be:

  • itself (#define TEST1 TEST1). In that case, it will consider TEST1 to be undefined
  • string, negative integer, floating point literals, arrays, structures, operators/punctuation, complex expressions, etc. In those cases, it will fail with a preprocessor error
  • a function-like macro (whatever its expansion), for example #define TEST1() 1

The first two limitations are, as far as I am aware, unavoidable, given the facilities provided by the standard C preprocessor. (I'm uncertain if supporting function-like macros are possible, but I doubt many people will have that requirement.)

One potential gotcha: if we forget to properly define the COMPARE_DEFINED__ helper macro for some identifier X, it will fail silently (treat X as undefined even though it is actually defined). We can modify it to issue a preprocessor error if the helper macro is missing:

#define ERROR(err) FATAL_ERROR_##err##!
#define ASSERT(cond,err) IF(cond)(EAT,ERROR)(err)
#define ASSERT_COMPARABLE(token) \
   ASSERT(IS_COMPARABLE(token),NOT_DEFINED__COMPARE_##token)
#define WHEN_DEFINED(token,then) \
   ASSERT_COMPARABLE(DEFINED__##token) \
   WHEN(NOT_EQUAL(DEFINED__##token,CAT(DEFINED__,token)))(then)

Now, if we try to do WHEN_DEFINED(TEST3,something()) without first properly defining COMPARE_DEFINED__TEST3(x), we'll get an preprocessor error, like this:

error: pasting formed 'FATAL_ERROR_NOT_DEFINED__COMPARE_DEFINED__TEST3!', an invalid preprocessing token

(Note: I tested this with Clang and GCC, and it worked with both. I don't know whether it works with other compilers, such as MSVC or Intel C++ Compiler (ICC); possibly not.)

Comments

0

I have seen the first answer by Nominal Animal, so I want to improve this answer in a perfect perspective.

If a macro not being defined, the macro TRACE will output an error string about the message, such as "This %s macro don't exist".

The code is there:

#include <stdio.h>
#include <string.h>
#define TRACE_STRINGIFY(item) "" #item
#define TRACE(macro, error_message, ...)                \
   do {                                                   \
        if (!strcmp("" #macro, TRACE_STRINGIFY(macro)))    \
        fprintf(stderr, "[ERROR]:[TRACE] (%s:%d) " error_message "\n", __FILE__, __LINE__, ##__VA_ARGS__);    \
    } while (0)

This is the main function and two test-macro definitions, to briefly test the usability of this TRACE macro.

#define dont_know_exist 52
//real_dont_exist macro
int main()
{
    TRACE(dont_know_exist, "This \"%s\" macro don't exist", "dont_know_exist");
    TRACE(real_dont_exist, "This \"%s\" macro don't exist", "real_dont_exist");
    return 0;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.