Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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;
};
like image 666
Jere.Jones Avatar asked Nov 14 '08 16:11

Jere.Jones


3 Answers

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.

like image 170
Johannes Schaub - litb Avatar answered Nov 02 '22 22:11

Johannes Schaub - litb


No friendship declaration needed:

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

template <typename T>
inline Logger& Display(T thing)
{
   *out_stream << thing;
    return *this;
}
private:
    std::ostream* out_stream;
};

template <typename T>
Logger& operator<< (Logger& logger, T thing) 
{
    return logger.Display(thing);
}
like image 2
James Curran Avatar answered Nov 02 '22 23:11

James Curran


Using a template is the right way to do it, but you just have to make sure the template is in the header file (logger.h, or whatever you called), not in the implementation file (logger.cpp). This will automatically work for any type which has operator << defined with an std::ostream. It will also automatically work with stream manipulator objects - those are really just functions which take an std::ostream parameter, and operator << just calls the function on the ostream.

You can make operator << a friend function as follows:

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

Specializations are easy - just use template specializations (again, in the header file):

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

// Template specialization - the "template <>" part is necessary
template <>
Logger& operator<< (Logger& logger, const wchar_t *wstr)
{
  // convert wstr to an ANSI string and log it
}

template <>
Logger& operator<< (Logger& logger, const std::wstring & wstr)
{
  // convert wstr to an ANSI string and log it
}
like image 2
Adam Rosenfield Avatar answered Nov 03 '22 00:11

Adam Rosenfield