Search code examples
c++macrosconditional-compilationdefined

Strange behaviour of #ifdef defined() in C++


I was writing cross platform code in Visual Studio Community, using C++20 and I am stuck on the output of following code:


#define WINDOWS (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__))
#define UNIX (defined(__unix__) || defined(__unix))

constexpr bool is_unix1()
{
#ifdef UNIX
    return true;
#else
    return false;
#endif
}

constexpr bool is_unix2()
{
#ifdef defined(UNIX)
    return true;
#else
    return false;
#endif
}


int main()
{
    cout << std::boolalpha << is_unix1() << " " << is_unix2() << endl;
}

When I ran this code in windows, from inside VS Community itself, I got the following output:

true false

Can someone explain why is_unix1() is evaluated as false while is_unix2() is evaluated as true?

I came to know that defined(...) is compiler specific, and it is not part of standard C++. But directly using the macro is resulting in strange behavior, and I am stuck on which approach to use here.


Solution

  • defined is not compiler-specific. It is specified in the C++ standard.

    #ifdef defined(UNIX) is simply not valid syntax and a compiler must diagnose this. #ifdef must be followed by a single identifier and then a new-line. It conditionally compiles code based on whether or not the identifier is defined as a macro.

    #ifdef UNIX therefore always compiles the then-branch, because you defined UNIX as a macro beforehand.

    What you want seems to be something like

    #if (defined(__unix__) || defined(__unix))
    

    #if conditionally compiles based on a given expression which can include defined operators, which individually evaluate to 0 or 1 depending on whether the identifier is defined as a macro.

    However, you can't hide (defined(__unix__) || defined(__unix)) behind the UNIX macro and then have it expand in the controlling #if expression. If such an expansion results in the defined token, the behavior of the program is undefined.

    So what you really want instead of your definition for UNIX is

    #if (defined(__unix__) || defined(__unix))
    #define UNIX
    #endif
    

    And then later

    #ifdef UNIX