I can pass a template parameter to an object's member function, but not to the object itself.
Is this possible at all?
#include <iostream>
#include <string>
using namespace std;
class Logger {
public:
Logger& operator()(int t) {
cerr << "(tag " << t << ") ";
return *this;
}
Logger& operator<<(const string &s) {
cerr << s << endl;
return *this;
}
template<int t> Logger& Tag() {
cerr << "(tag " << t << ") ";
return *this;
}
Logger() {}
template<int t> Logger() {
cerr << "(tag " << t << ") ";
}
};
int main() {
{
Logger log;
log(2) << "World";
log(3) << "World";
log.Tag<2>() << "Template 1";
log<2> << "Template 2"; // <-- error
}
}
This is the error message (from GCC 13.2):
error: invalid operands to binary expression ('Logger' and 'int')
log<2> << "Template 2";
~~~^~
What I would like to achieve is to have log<2> and log.Tag<2> behave in the same way.
The only way to achieve the log<i> << ...
syntax is to make log
a variable template. Note that this will break log << ...
and log.Foo()
syntax.
#include <iostream>
#include <string>
class Logger
{
int loglevel = 0;
public:
Logger(int loglevel) : loglevel(loglevel) {}
Logger& operator<<(const std::string &s)
{
std::cerr << loglevel << "->" << s << '\n';
return *this;
}
};
template <int N> Logger log(N);
int main()
{
log<2> << "blah";
}
This would give you N "loggers", one per log level, so they should probably hold pointers to the one single underlying logger.
I don't think this is a good idea overall. Firstly, too much effort for a tiny bit of syntax sugar. And second, in the modern day you should probably design your logger around std::format
, which means all your logging statements become function calls, such as:
log(i, "format", args...);
log<i>("format", args...);
And this call syntax is trivial to implement.