views:

382

answers:

3

Hi guys,

I'm trying to implement my own stream manipulator inside my logging class. It's basically endline manipulator which changes state of a flag. However when I try to use it, I'll get:

ftypes.cpp:57: error: no match for ‘operator<<’ in ‘log->Log::debug() << log->Log::endl’
/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../../include/c++/4.1.2/bits/ostream.tcc:67: note: candidates are: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>& (*)(std::basic_ostream<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../../include/c++/4.1.2/bits/ostream.tcc:78: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ios<_CharT, _Traits>& (*)(std::basic_ios<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../../include/c++/4.1.2/bits/ostream.tcc:90: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::ios_base& (*)(std::ios_base&)) [with _CharT = char, _Traits = std::char_traits<char>]

...

Code:

class Log {
public:
  ...
  std::ostream& debug() { return log(logDEBUG); }  
  std::ostream& endl(std::ostream& out);           // manipulator
  ...
private:
  ...
  std::ofstream m_logstream;
  bool          m_newLine;
  ...
}


std::ostream& Log::endl(std::ostream& out) 
{  
  out << std::endl;
  m_newLine = true;
  return out;
}

std::ostream& Log::log(const TLogLevel level)
{
  if (level > m_logLevel) return m_nullstream;

  if (m_newLine)
  {
    m_logstream << timestamp() << "|" << logLevelString(level) << "|";
    m_newLine = false;
  }
  return m_logstream;
}

I'm getting the error when I try to call it:

log->debug() << "START - object created" << log->endl;

(log is the pointer to Log object)

Any ideas? I suspect it's somehow connected to the fact that the manipulator is actually inside the class but that's just my wild guess...

Cheers,

Tom

EDIT: Putting this here instead of comment because of limiting formatting. I tried to implement my streambuf and it works great with one exception: when I try to open filebuf for append it fails. Output works nicely, just append doesn't for some unknown reason. If I try to use ofstream directly with append it works. Any idea why? – Works:

std::ofstream test; 
test.open("somefile", std::ios_base::app); 
if (!test) throw LogIoEx("Cannon open file for logging"); 
test << "test" << std::endl;

Appends "test" correctly .

Doesn't work:

std::filebuf *fbuf = new std::filebuf(); 
if (!fbuf->open("somefile", std::ios_base::app)) throw LogIoEx("Cannon open file for logging");

Throws exception, if I set openmode to out then it works..

Cheers

+5  A: 

There is defined an operator<<(ostream &, ostream &(*)(ostream&)) but not an operator<<(ostream &, ostream &(Log::*)(ostream&)). That is, the manipulator would work if it were a normal (non-member) function, but because it depends on an instance of Log, the normal overload wouldn't work.

To fix this problem, you may need to have log->endl be an instance to a helper object and, when pushed with operator<<, call the appropriate code.

Like so:

class Log {
  class ManipulationHelper {  // bad name for the class...
  public:
    typedef ostream &(Log::*ManipulatorPointer)(ostream &);

    ManipulationHelper(Log *logger, ManipulatorPointer func) :
      logger(logger),
      func(func) {
    }

    friend ostream &operator<<(ostream &stream, ManipulationHelper helper) {
        // call func on logger
        return (helper.logger)->*(helper.func)(stream);
    }

    Log *logger;
    ManipulatorPointer func;
  }

  friend class ManipulationHelper;

public:
  // ...

  ManipulationHelper endl;

private:
  // ...

  std::ostream& make_endl(std::ostream& out); // renamed
};

// ...

Log::Log(...) {
  // ...
  endl(this, make_endl) {
  // ...
}
strager
+2  A: 

That's not how manipulators work - it's all about types. What you want is something like:

class Log {
...
struct endl_tag { /* tag struct; no members */ };
static const struct endl_tag endl;
...
LogStream &debug() { /* somehow produce a LogStream type here */ }
}

LogStream &operator<<(LogStream &s, const struct endl_tag &) {
  s.m_newLine = true;
}

Note that:

  1. Since m_newLine is part of Log, we can't be working with generic std::ostreams. After all, what would std::cout << Log->endl() mean? So you need to create a new stream type derived from std::ostream (I've left it out here, but assumed it's named LogStream).
  2. endl doesn't actually do anything itself; all the work is in operator<<. The only purpose of it is to get the right operator<< overload to run.

That said, you're not supposed to be defining new manipulators and stream classes if you can avoid it, because it gets complex :) Can you do what you need using just std::endl, and wrapping an ostream around your own custom streambuf? That is how the C++ IO library is meant to be used.

bdonlan
+1 for suggesting a subclass instead of using magic.
strager
Actually, `std::endl` in fact does work. However, it does so indirectly. First, by passing it as an argument, the right `operator<<` overload is called. This overload then in turns calls `std::endl()`
MSalters
Thanks guys for the answers. I can see that I was completely wrong. What I was trying to achieve was to know if the stream was flushed so that I can write the "header" in the log (time, loglevel, etc.). I was looking for some simple way to do it. So you are saying that the best way to go is write my own streambuf (NOT ostream)? Isn't it too low level for such simple thing? Cheers
Tom
The difference between `streambuf` and `ostream` is `streambuf` just gets raw, unformatted bytes, while `ostream` handles formatting without being overly concerned about how the underlying streambuf is going to get that output. I think `streambuf` is exactly where you want to be, simply because you really don't need to take any special formatting action. You just need to insert log header data after each `'\n'`, which is something that can be done at the `streambuf` level easily enough.
bdonlan
Ok, I tried to implement my streambuf and it works great with one exception: when I try to open filebuf for append it fails. Output works nicely, just append doesn't for some unknown reason. If I try to use ofstream directly with append it works. Any idea why?
Tom
Works:std::ofstream test;test.open("somefile", std::ios_base::app);if (!test) throw LogIoEx("Cannon open file for logging");test << "test" << std::endl;appends "test" correctlyDoesn't workstd::filebuf *fbuf = new std::filebuf();if (!fbuf->open("somefile", std::ios_base::app)) throw LogIoEx("Cannon open file for logging");throws exception, if I set openmode to out then it works...
Tom
Hm, that's a mess? How did you manage to get formatted comments?
Tom
Comments only support very limited formatting - if you're going to be posting long code samples, it's best to open a new question (or if it's directly related to your existing one, edit it)
bdonlan
Ok, I edited the post and added it there...
Tom
A: 

Try this:

#include <iostream>

class Log
{
    public:
    class LogEndl
    {
        /*
         * A class for manipulating a stream that is associated with a log.
         */
        public:
            LogEndl(Log& p)
                :parent(p)
            {}
        private:
            friend std::ostream& operator<<(std::ostream& str,Log::LogEndl const& end);
            Log&    parent;
    };
    std::ostream& debug()   {return std::cout;}
    /*
     * You are not quite using manipulators the way they are entended.
     * But I wanted to give an example that was close to your original
     *
     * So return an object that has an operator << that knows what to do.
     * To feed back info to the Log it need to keep track of who its daddy is.
     */
    LogEndl       endl()    {return LogEndl(*this);}
    private:
        friend std::ostream& operator<<(std::ostream& str,Log::LogEndl const& end);
        bool    endOfLine;

};


std::ostream& operator<<(std::ostream& str,Log::LogEndl const& end)
{
    // Stick stuff on the stream here.
    str << std::endl;

    // Make any notes you need in the log class here.
    end.parent.endOfLine    = true;

    return str;
};

int main()
{
    Log     log;

    /*
     * Please note the use of objects rather than pointers here
     * It may help
     */
    log.debug() << "Debug " << log.endl();
}
Martin York
Cheers, this looks close enough but I can see I missed the point and I'd like to do it properly now :))
Tom