10

For code:

#if defined(FOO) && FOO(foo)
    #error "FOO is defined."
#else
    #error "FOO is not defined."
#endif

MSVC 19.38 prints:

<source>(1): warning C4067: unexpected tokens following preprocessor directive - expected a newline
<source>(4): fatal error C1189: #error:  "FOO is not defined."

ICX 2024.0.0 and Clang 18.1 prints:

<source>:1:21: error: function-like macro 'FOO' is not defined
    1 | #if defined(FOO) && FOO(foo)
      |                     ^
<source>:4:6: error: "FOO is not defined."
    4 |     #error "FOO is not defined."
      |      ^
2 errors generated.

GCC 14.1 prints:

<source>:1:24: error: missing binary operator before token "("
    1 | #if defined(FOO) && FOO(foo)
      |                        ^
<source>:4:6: error: #error "FOO is not defined."
    4 |     #error "FOO is not defined."
      |      ^~~~~
Compiler returned: 1

Why does every compiler but MSVC print an error about an undefined macro when FOO is not undefined (although MSVC prints a warning too)? Is there some special semantic that I am not seeing here?

FOO(foo) should not be evaluated if defined(FOO) evaluates to false.

2
  • This is a problem for C23's __has_include and similar macros. And was raised by a reviewer on the CodeReview website.
    – Harith
    Commented 2 days ago
  • 3
    One way to think about it is that even though the second operand may not be evaluated, it still has to be parsed successfully, and the syntax rules are different in the preprocessor than in C itself. The "short circuit" behavior in C is really about side effects that the second operand expression may have, and this is irrelevant to the preprocessor because there, expressions don't ever have side effects. Commented 2 days ago

1 Answer 1

18

If FOO is not defined (or is defined but not as a function-like macro), then FOO(foo) is a syntax error.

The #if directive expects an integer constant expression to follow it (including expressions of the form "defined identifier"). Since FOO(foo) can't be expanded due to FOO not being defined, this is not an integer constant expression.

You would get a similar error for something like this:

int main()
{
    int x = some_valid_expression && undeclared_identifier;
    return 0;
}

To do what you want, you need to break up the #if directive into multiple ones:

#if defined(FOO)
    #if FOO(foo)
        #error "FOO is defined and non-zero."
    #else
        #error "FOO is zero."
    #endif
#else
    #error "FOO is not defined."
#endif
3
  • Interesting. How then do I write #if defined(__has_include) && __has_include(<stdbit.h>) in a manner that wouldn't cause compilation failure if the macro __has_include was not defined?
    – Harith
    Commented 2 days ago
  • 7
    @Harith: Use nested #if directives. Commented 2 days ago
  • 2
    @Harith: The C23 draft standard has an 'EXAMPLE 2' that illustrates: /* Fallback for compilers not yet implementing this feature. */#ifndef __has_c_attribute#define __has_c_attribute(x) 0#endif /* __has_c_attribute */#if __has_c_attribute(fallthrough)#endif. A similar operation could be used for __has_include (and __has_embed). Commented 2 days ago

Not the answer you're looking for? Browse other questions tagged or ask your own question.