5
votes

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;
};
4
Your endresult is wrong. specializations in class scope are not allowed :) just overload them instead (omit the template<> part) It's required in Adam's answer (which specializes them at namespace scope) since otherwise the (then normal operator functions) are not friends anymore.Johannes Schaub - litb
Interestingly, it worked with them there. But, just to be correct, I removed them anyway. Thanks!Jere.Jones

4 Answers

7
votes

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.

2
votes

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
}
2
votes

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);
}
0
votes

why not do it the printf way and use the multi parameter method (with the three dots ...). This still gives you alot of formating power and doesnt make it that messy as when you are using the <<.

For example:

Logger("This is my log msg %0X", 45);

Hang on two secs and ill pull up a code sample for you.

Edit:

void Logger(const char* format, ...)
{
    char szMsg[3000];

    va_list args;
    va_start( args, format );
    vsnprintf( szMsg, sizeof(szMsg) - 1, format, args );
    va_end(args);

    // code to print szMsg to a file or whatever here
}

If you want to use this as a class not a stand alone function you could overload the logger operator () and it will work just the same