Home » C++ » Why does the C preprocessor consider enum values as equal?

Why does the C preprocessor consider enum values as equal?

Posted by: admin November 30, 2017 Leave a comment

Questions:

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?

Answers:

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

Questions:
Answers:

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.

Questions:
Answers:

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.

Questions:
Answers:

The preprocessor runs before the compiler, which means that the preprocessor doesn’t know anything about symbols defined by the compiler and therefore it can’t act depending on them.

Questions:
Answers:

As the other answers said, the C preprocessor doesn’t see enums. It expects, and can only understand, macros.

Per the C99 standard, §6.10.1 (Conditional inclusion):

After all replacements due to macro expansion and the defined unary operator have been performed, all remaining identifiers are replaced with the pp-number 0

In other words, in an #if or #elif directive, any macros that cannot be expanded, because they don’t exist/are undefined, will behave exactly as if they’d been defined as 0, and therefore will always be equal to each other.

You can catch likely unintended behavior like this in GCC/clang with the warning option -Wundef (you’ll probably want to make it fatal with -Werror=undef).

Questions:
Answers:

Other answers explain why what you’re trying doesn’t work; for an alternative, I’d probably go with:

#define RGB 1
#define GREY 2
#define CMYK 3
#define MODE RGB

#if MODE == RGB
    //RGB-mode code
#elif MODE == GREY
    //Greyscale code
#elif MODE == CMYK
    //CMYK code
#else
#    error Undefined MODE
#endif

You might want prefixes on the RGB/GREY/CMYK if there’s a danger of clashes with “real” source code.

Questions:
Answers:

The posts have explained why, but a possible solution for you that keeps readability might be like this

#define MODE_RGB

int main()
{        
    #ifdef MODE_RGB
        std::cout << "RGB mode" << std::endl;
    #elif defined MODE_GREY
        std::cout << "Grey mode" << std::endl;
    #elif defined MODE_CMYK
        std::cout << "CMYK mode" << std::endl;
    #endif
}

You just then need to change the macro at the top, to only the macro you are interested in is defined. You could also include a check to make sure that one and only one is defined and if not then and do #error "You must define MODE_RGB, MODE_GREY or MODE_CMYK