Search code examples
c++c++17operator-overloading

How to overload template operator+ to work with const char* and const char[]?


My question may be open to interpretation cause I`m not sure about a specific error I've encountered. So I'll appreciate any idea or even solution.

Problem :

I'm working on my custom Logger class, based on this repo. I've made many changes, including adding overloaded operator+ to assemble/push incoming log_txt into std::map (K - thread_id, V - string_log).

So:

  1. operator() - add time and status to the std::map;
  2. operator+ - MUST assemble all text and values and push resulting string into std::map;
  3. operator<< - release log at once to avoid text conflicts between logs from other threads.
log(LOG_WARN) << "thread ONE : " + to_string(1111) + " val\n";
         ///> Will work but than i have to implement all possible types cast...
         ///> I'm trying to keep the code easy to read. 

log(LOG_WARN) << "thread ONE : " + 1111 + " val\n";
         ///> Won't work --> error: invalid operands of types ‘const char*’ and ‘const char [6]’ to binary ‘operator+’
         ///> comment: "thread ONE : " + 1111 - const char* and  + "val\n" - const char[6]

My overloaded operator+ as func-member of the Logger class:

template <typename T>
Logger& Logger::operator+(const T& s){
    lock_guard<mutex> lock(_mutex);
    if (auto search = _th_streams.find(this_thread::get_id()); search != _th_streams.end()){
            stringstream ss;
            ss << s;
            search->second.append(ss.str());
            return *this;
    }
    return *this;
}

I'm sure, my operator+ implementation may be far from ideal but I'm curious is it possible to solve the error by editing only my operator+?

Overload ooperator+ or to_string()


Solution

  • Because of operator precedence, you'll need to adjust something in the requirements.

    I would go for

    log(LOG_WARN) << "thread ONE : " << 1111 << " val";
    

    which is recognizable by most C++ programmers.

    I would rewrite the requirements so that

    1. operator() - Acquires, in a thread-safe manner, a reference to the std::string in the std::map that is unique for this thread and adds time and status to it.
    2. operator<< - Appends to the std::string above.
    3. At the end of the full expression, additional measures should be possible if needed.

    The additional measures could be to send the log entry to a central logger. In my example I just add \n to mark the end of this logging entry.

    • The std::map is here only locked while fetching the std::string from it. Logging to the individual std::strings in the map can then be done without locking.
    • operator() returns a proxy object (expr) that keeps a reference to the std::string in the std::map
    • At the end of the full expression, the proxy object dies and can then perform any additional finalization that is needed.
    class Logger {
        struct expr {
            ~expr() {
                // the end of the full logging expression
                th_stream += '\n';
            }
            template <class T>
            expr& operator<<(const T& s) {
                std::ostringstream ss;
                ss << s;
                th_stream.append(ss.str());
                return *this;
            }
            
            std::string& th_stream;
        };
    
    public:
        expr operator()(loglevel level) {        
            std::string& th_stream = [&]() -> std::string& {
                std::lock_guard<std::mutex> lock(_mutex);
                return _th_streams[std::this_thread::get_id()];
            }();
            th_stream += std::string("TIMESTAMP ") + std::to_string(level) + ' ';
            return {th_stream};
        }
    
    private:
        std::mutex _mutex;
        std::map<std::thread::id, std::string> _th_streams;
    };
    

    Demo