Search code examples
c++objecttemplates

How to pass a template parameter to an object without calling its member functions?


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.


Solution

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