In our project we use the c++ stream operator (<<) in our object model to print out an easy readible format of the data. A simplified example is this:
std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) {
oStream << "[SomeMember1: " << iOwnClass._ownMember1 << "]\n";
oStream << "[SomeMember2: " << iOwnClass._ownMember2 << "]\n";
}
Resulting in this in the logging:
[SomeMember1: foo]
[SomeMember2: bar]
What we want now is to be able to indent the result of that operator. Some calling class might not want the result like this, but want to add 2 spaces indention before each line. We could add a member to our class specifying the indention, but that does not seem to be an elegant solution.
Of course this is not a very big issue, but our logging would be so much nicer if this would work.
Thanks
The simplest solution is to slip a filtering streambuf between the
ostream
and the actual streambuf. Something like:
class IndentingOStreambuf : public std::streambuf
{
std::streambuf* myDest;
bool myIsAtStartOfLine;
std::string myIndent;
std::ostream* myOwner;
protected:
virtual int overflow( int ch )
{
if ( myIsAtStartOfLine && ch != '\n' ) {
myDest->sputn( myIndent.data(), myIndent.size() );
}
myIsAtStartOfLine = ch == '\n';
return myDest->sputc( ch );
}
public:
explicit IndentingOStreambuf(
std::streambuf* dest, int indent = 4 )
: myDest( dest )
, myIsAtStartOfLine( true )
, myIndent( indent, ' ' )
, myOwner( NULL )
{
}
explicit IndentingOStreambuf(
std::ostream& dest, int indent = 4 )
: myDest( dest.rdbuf() )
, myIsAtStartOfLine( true )
, myIndent( indent, ' ' )
, myOwner( &dest )
{
myOwner->rdbuf( this );
}
virtual ~IndentingOStreambuf()
{
if ( myOwner != NULL ) {
myOwner->rdbuf( myDest );
}
}
};
To insert, just create an instance of the streambuf:
IndentingOStreambuf indent( std::cout );
// Indented output...
When indent
goes out of scope, everything returns to normal.
(For logging, I have one that is a bit more complex: the
LoggingOStreambuf
takes __FILE__
and __LINE__
as arguments, sets
myIndent
to a formatted string with these arguments, plus a time
stamp, resets it to an indentation string after each output, collects
all of the output in an std::ostringstream
, and outputs it atomically
to myDest
in the destructor.)
This can be done using a custom stream-manipulator that stores the desired indentation-level in a word of the internal extensible array of the stream. You can request such a word using the function ios_base::xalloc
. This function will give you the index of your word. You can access it using ios_base::iword
. One way to implement that would be this:
struct indent {
indent(int level) : level(level) {}
private:
friend std::ostream& operator<<(std::ostream& stream, const indent& val);
int level;
};
std::ostream& operator<<(std::ostream& stream, const indent& val) {
for(int i = 0; i < val.level; i++) {
stream << " ";
}
return stream;
}
std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) {
oStream << indent(oStream.iword(index)) << "[SomeMember1: " <<
iOwnClass._ownMember1 << "]\n";
oStream << indent(oStream.iword(index)) << "[SomeMember2: " <<
iOwnClass._ownMember2 << "]\n";
}
You'd have to figure out where to store the index
. This effectively allows you to add custom state to the stream (note that this would not be thread-safe out-of-the-box). Every function that wants indentation should add the requested indentation to the stream, and subtract it again when it is done. You could make sure this always happen by using a guard to add/subtract the desired indent (IMHO this is more elegant than using a manipulator):
class indent_guard {
public:
indent_guard(int level, std::ostream& stream, int index)
: level(level),
stream(stream),
index(index)
{
stream.iword(index) += level;
}
~indent_guard() {
stream.iword(index) -= level;
}
private:
int level;
std::ostream& stream;
int index;
};
You could use it like this:
void some_func() {
indent_guard(2, std::cout, index);
// all output inside this function will be indented by 2 spaces
some_func(); // recursive call - output will be indented by 4 spaces
// here it will be 2 spaces again
}
Not so good way to do this is to add a global variable, which tells the indentation. Something like this :
std::string OwnClassIndentation;
std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) {
oStream << "[SomeMember1:" << OwnClassIndentation << iOwnClass._ownMember1 << "]\n";
oStream << "[SomeMember2:" << OwnClassIndentation << iOwnClass._ownMember2 << "]\n";
}
And then set it as appropriate.
You can make your own stream class that has an indentation variable and override the endl for that class, inserting the indentation.