c++enumsoperator-overloadingoperatorsenum-class

operator<< for enum class just doesn't work


I have the following code:

logging.hxx

#include <iostream>
#include <string>
#include <sstream>

#include "singleton.hxx"
#include "utils.hxx"

class FGaugeLogger: public Singleton<FGaugeLogger> {
    public:
        enum class LogLevel: int {
            DEBUG = 0,
            INFO = 1,
            WARNING = 2,
            ERROR = 3,
            FATAL = 4,
        };
        
        FGaugeLogger() {};
        
        template<typename T>
        void log(LogLevel level, const T& msg) {
            if (level >= _level) {
                if (level >= LogLevel::WARNING) {
                    std::cerr << "[" << level << "] " << msg << std::endl;
                } else {
                    std::cout << "[" << level << "] " << msg << std::endl;
                }
            }
        }
    
    private:
        LogLevel _level {LogLevel::DEBUG};
};

std::ostream& operator<<(std::ostream& s, const FGaugeLogger::LogLevel level);

#define LOG(level, expr) \
    std::stringstream ss; \
    ss << expr; \
    std::string str = ss.str(); \
    FGaugeLogger::instance()->log(FGaugeLogger::LogLevel::level, ss.str());

logging.cxx

#include "logging.hxx"

std::ostream& operator<<(std::ostream& s, FGaugeLogger::LogLevel level) {
    s << "level";
    return s;
}

main.cxx

#include "logging.hxx"

int main(int argc, char* argv[]) {
    LOG("info message");
    return 0;
}

This gives the following message when compiled, followed by 100s of overloads that all don't match:

In file included from /media/frederic/WD-5TB/.fg/Programme/fgauge/src/main.cxx:1:
/media/frederic/WD-5TB/.fg/Programme/fgauge/src/logging.hxx: In member function ‘void FGaugeLogger::log(FGaugeLogger::LogLevel, const T&)’:
/media/frederic/WD-5TB/.fg/Programme/fgauge/src/logging.hxx:27:58: error: no match for ‘operator<<’ (operand types are ‘std::basic_ostream<char>’ and ‘FGaugeLogger::LogLevel’)
   27 |                                         std::cerr << "[" << level << "] " << msg << std::endl;
      |                                         ~~~~~~~~~~~~~~~~ ^~ ~~~~~
      |                                                   |         |
      |                                                   |         FGaugeLogger::LogLevel
      |                                                   std::basic_ostream<char>

Any ideas what could be wrong here ? AFAICS my code is identical to the third example in this answer to a similar question.


Solution

  • The problem is that C++ files are parsed from top to bottom, and the order of things generally matters. At the point where you have written:

    std::cerr << "[" << level << "] " << msg << std::endl;
    

    The operator<< for FGaugeLogger::LogLevel hasn't been declared yet.

    A simple fix would be to declare it as a friend inside the class:

    class FGaugeLogger: public Singleton<FGaugeLogger> {
      public:
        enum class LogLevel: int {
            DEBUG = 0,
            INFO = 1,
            WARNING = 2,
            ERROR = 3,
            FATAL = 4,
        };
        // note: parameter names are optional, and it's probably better to omit them
        friend std::ostream& operator<<(std::ostream&, FGaugeLogger::LogLevel);
        // ...
    };
    

    See minimal example at Compiler Explorer

    You could also define it inside the class, making it a hidden friend (see Hidden friends: declarations and definitions).

    Alternative Solution

    You could also define log out-of-line after the definition of operator<<, like

    template<typename T>
    void FGaugeLogger::log(LogLevel level, const T& msg) { /* ... */ }
    

    However, this is overall more work compared to the first solution, which only required you to move code into the class and make it friend. Defining the member function template out-of-line would require some code duplication.