Why does the C preprocessor consider enum values as equal?

Why does the std::cout line in the following code run even though A and B are different?

#include <iostream>

enum T { A = 1, B = 2 };
// #define A 1
// #define B 2

int main() {
#if (A == B)
    std::cout << A << B;
#endif
}

If I use #define instead (as commented out), I get no output as I expect.

Reason for the question:

I want to have a mode selector for some test code in which I can easily change modes by commenting/uncommenting lines on top:

enum T { MODE_RGB = 1, MODE_GREY = 2, MODE_CMYK = 3 };
// #define MODE MODE_RGB
#define MODE MODE_GREY
// #define MODE MODE_CMYK

int main() {
#if (MODE == MODE_RGB)
    // do RGB stuff
#elif (MODE == MODE_GREY)
    // do greyscale stuff
#else
    // do CMYK stuff
#endif

    // some common code

    some_function(arg1, arg2,
#if (MODE == MODE_RGB)
        // RGB calculation for arg3,
#elif (MODE == MODE_GREY)
        // greyscale calculation for arg3,
#else
        // CMYK calculation for arg3,
#endif
        arg4, arg5);
}

I know I can use numeric values e.g.

#define MODE 1 // RGB
...
#if (MODE == 1) // RGB

but it makes the code less readable.

Is there an elegant solution for this?


Solution 1:

There are no macros called A or B, so on your #if line, A and B get replaced by 0, so you actually have:

enum T { A = 1, B = 2 };

int main() {
#if (0 == 0)
    std::cout << A << B;
#endif
}

The preprocessor runs before the compiler knows anything about your enum. The preprocessor only knows about macros (#define).

Solution 2:

This is because the preprocessor works before compile time.

As the enum definitions occur at compile time, A and B will both be defined as empty (pp-number 0) - and thus equal - at pre-processing time, and thus the output statement is included in the compiled code.

When you use #define they are defined differently at pre-processing time and thus the statement evaluates to false.

In relation to your comment about what you want to do, you don't need to use pre-processor #if to do this. You can just use the standard if as both MODE and MODE_GREY (or MODE_RGB or MODE_CMYK) are all still defined:

#include <iostream>

enum T { MODE_RGB = 1, MODE_GREY = 2, MODE_CMYK = 3 };

#define MODE MODE_GREY

int main()
{
    if( MODE == MODE_GREY )
        std::cout << "Grey mode" << std::endl;
    else if( MODE == MODE_RGB )
        std::cout << "RGB mode" << std::endl;
    else if( MODE == MODE_CMYK )
        std::cout << "CMYK mode" << std::endl;

    return 0;
}

The other option using only the pre-processor is to do this as @TripeHound correctly answered below.

Solution 3:

Identifiers that are not defined macros are interpreted as value 0 in conditional preprocessor directives. Therefore, since you hadn't defined macros A and B, they are both considered 0 and two 0 are equal to each other.

The reason why undefined (to the pre-processor) identifiers are considered 0 is because it allows using undefined macros in the conditional without using #ifdef.