Custom C++ manipulator problem
-
20-09-2019 - |
Question
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:
- Since m_newLine is part of
Log
, we can't be working with genericstd::ostream
s. After all, what wouldstd::cout << Log->endl()
mean? So you need to create a new stream type derived fromstd::ostream
(I've left it out here, but assumed it's namedLogStream
). endl
doesn't actually do anything itself; all the work is inoperator<<
. The only purpose of it is to get the rightoperator<<
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.
OTHER TIPS
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) {
// ...
}
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();
}