Search code examples
c++streammanipulators

Custom C++ manipulator problem


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


Solution

  • 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.