I have the following code:
#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());
#include "logging.hxx"
std::ostream& operator<<(std::ostream& s, FGaugeLogger::LogLevel level) {
s << "level";
return s;
}
#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.
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).
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.