C++ – How to overload operator<< that doesn't take or return ostream

coperator-overloadingtemplates

Original Question

I am writting a logging class where the goal is to be able to do this:

// thread one
Logger() << "Some string" << std::ios::hex << 45;
// thread two
Logger() << L"Some wide string" << std::endl;

Currently my Logger header looks something like this:

#pragma once;
#include <ostream>    
class Logger
{
public:
    Logger();
    ~Logger();

    std::ostream* out_stream;
};

template <typename T>
Logger& operator<< (Logger& logger, T thing) {
    *logger.out_stream << thing;
    return logger;
}

Some notes about this class:

  1. Cross platform compatibility is not an issue.
  2. Inside of Logger.cpp there is a singleton class that takes care of creating the "real" ostream.
  3. The Logger constructor and deconstructor perform the necessary locking of the singleton.

I have three problems:

  • How do I make the operator<< function a friend or member so I can set out_stream as private?
  • How do I make the operator<< function work for manipulators?
  • How can I add a specialization so that if T is a WCHAR* or std::wstring that it will convert it to char* or std::string before passing it to out_stream? (I can do the conversion. Losing high unicode characters isn't a problem in my case.)

Summary of things learned in answers:

  • Put template BEFORE friend instead of after.
  • std::ios::hex is not a manipulator. std::hex is a manipulator.

End Result

#pragma once
#include <ostream>
#include <string>

std::string ConvertWstringToString(std::wstring wstr);

class Logger
{
public:
    Logger();
    ~Logger();

    template <typename T>
    Logger& operator<< (T data) {
        *out << data;
        return *this;
    }
    Logger& operator<< (std::wstring data) {
        return *this << ConvertWstringToString(data);
    }
    Logger& operator<< (const wchar_t* data) {
        std::wstring str(data);
        return *this << str;
    }

private:
    std::ostream* out;
};

Best Answer

You can use friend definition, which will define the operator in the surrounding namespace of the class, and make it only visible to operator overloading resolution (not callable manually using the ::operator<<... syntax):

class Logger
{
public:
    Logger();
    ~Logger();

    std::ostream* out_stream;

    template <typename T>
    friend Logger& operator<< (Logger& logger, T thing) {
        *logger.out_stream << thing;
        return logger;
    }

    /* special treatment for std::wstring. just overload the operator! No need
     * to specialize it. */
    friend Logger& operator<< (Logger& logger, const std::wstring & wstr) {
        /* do something here */
    }

};

The alternative, to keep your code as it is and just make the operator<< template a friend, you add this line into your class definition:

template <typename T>
friend Logger& operator<< (Logger& logger, T thing);

For the manipulator problem, i will just give you my code i write some time ago:

#include <iostream>
#include <cstdlib>
using namespace std;

template<typename Char, typename Traits = char_traits<Char> >
struct logger{
    typedef std::basic_ostream<Char, Traits> ostream_type;
    typedef ostream_type& (*manip_type)(ostream_type&);
    logger(ostream_type& os):os(os){}
    logger &operator<<(manip_type pfn) {
        if(pfn == static_cast<manip_type>(std::endl)) {
            time_t t = time(0);
            os << " --- " << ctime(&t) << pfn; 
        } else
            os << pfn;
        return *this; 
    }
    template<typename T> 
    logger &operator<<(T const& t) { 
        os << t; 
        return *this;
    }
private:        
    ostream_type & os;
};

namespace { logger<char> clogged(cout); }
int main() { clogged << "something with log functionality" << std::endl; }

};

Note that it is std::hex , but not std::ios::hex. The latter is used as a manipulator flag for the setf function of streams. Note that for your example, tho, no special treatment of manipulators is required. The above special treatment of std::endl is only needed because i do stream the time in addition when std::endl is used.

Related Topic