Search code examples
c++preprocessorc++03

How to use preprocessor IF on DEFINE that is an ENUM member?


I am struggling with this for a while now, and cant get it to work!

I have a preprocessor define for LOG_LEVEL which defines what logs my program should emit.
I Have a lot of LOG points, so performance is needed,
therefore, no use of runtime check for log_level.

I trimmed my code to the minimal problematic construct which can be played with (here)[https://onlinegdb.com/u39ueqNAI]:

#include <iostream>


typedef enum {
    LOG_SILENT=0,
    LOG_ERROR,
    LOG_WARNING,
    LOG_INFO,
    LOG_DEBUG
} E_LOG_LEVELS;

// this define is set using -DLOG_LEVEL=LOG_WARNING to the compiler.
#define LOG_LEVEL LOG_WARNING


int main() {
    std::cout << "Logging Level Value is " << LOG_LEVEL << std::endl; // output 2 (correctly)
    
    #if LOG_LEVEL==LOG_WARNING
    std::cout << "Log Level Warning!" << std::endl; // outputs (correctly)
    #endif
    
    #if LOG_LEVEL==LOG_ERROR
    std::cout << "Log Level Error!" << std::endl; // outputs (Why ??? )
    #endif
    
    return 0;
}

The main issue is that the #if LOG_LEVEL==LOG_* always true.

I also tried #if LOG_LEVEL==2 but this returned FALSE (uff).

what's going on ?

how can I test that a define is an enum value ?


Solution

  • You don't need the preprocessor for this. A normal

    if (LOG_LEVEL <= LOG_WARNING)
    

    will not create a runtime test when the condition involves only constants and the build has any optimization at all.

    Modern C++ allows you to force the compiler to implement the conditional at compile-time, using if constexpr (...). This will prune away dead branches even with optimization disabled.


    Finally, if you insist on using the preprocessor, and you can guarantee that the macro will use the symbolic name (you'll never build with g++ -DLOG_LEVEL=2), then you can

    #define STRINGIFY(x) #x
    #define STRINGY2(x) STRINGIFY(x)
    #define PP_LOG_LEVEL STRINGY2(LOG_LEVEL)
    #define LOG_LEVEL_IS(x) STRINGY2(LOG_LEVEL)==STRINGIFY(x)
    

    then either

    #if PP_LOG_LEVEL=="LOG_WARNING"
    

    or

    #if PP_LOG_LEVEL_IS(LOG_WARNING)
    

    But I would recommend avoiding preprocessor string comparison for this.

    If you do use the preprocessor, I recommend checking the actual value against a whitelist and using #error to stop the build in case LOG_LEVEL isn't set to any of the approved names.