How to easily indent output to ofstream?
This is the perfect situation to use a facet.
A custom version of the codecvt facet can be imbued onto a stream.
So your usage would look like this:
int main()
{
/* Imbue std::cout before it is used */
std::ios::sync_with_stdio(false);
std::cout.imbue(std::locale(std::locale::classic(), new IndentFacet()));
std::cout << "Line 1\nLine 2\nLine 3\n";
/* You must imbue a file stream before it is opened. */
std::ofstream data;
data.imbue(indentLocale);
data.open("PLOP");
data << "Loki\nUses Locale\nTo do something silly\n";
}
The definition of the facet is slightly complex.
But the whole point is that somebody using the facet does not need to know anything about the formatting. The formatting is applied independent of how the stream is being used.
#include <locale>
#include <algorithm>
#include <iostream>
#include <fstream>
class IndentFacet: public std::codecvt<char,char,std::mbstate_t>
{
public:
explicit IndentFacet(size_t ref = 0): std::codecvt<char,char,std::mbstate_t>(ref) {}
typedef std::codecvt_base::result result;
typedef std::codecvt<char,char,std::mbstate_t> parent;
typedef parent::intern_type intern_type;
typedef parent::extern_type extern_type;
typedef parent::state_type state_type;
int& state(state_type& s) const {return *reinterpret_cast<int*>(&s);}
protected:
virtual result do_out(state_type& tabNeeded,
const intern_type* rStart, const intern_type* rEnd, const intern_type*& rNewStart,
extern_type* wStart, extern_type* wEnd, extern_type*& wNewStart) const
{
result res = std::codecvt_base::noconv;
for(;(rStart < rEnd) && (wStart < wEnd);++rStart,++wStart)
{
// 0 indicates that the last character seen was a newline.
// thus we will print a tab before it. Ignore it the next
// character is also a newline
if ((state(tabNeeded) == 0) && (*rStart != '\n'))
{
res = std::codecvt_base::ok;
state(tabNeeded) = 1;
*wStart = '\t';
++wStart;
if (wStart == wEnd)
{
res = std::codecvt_base::partial;
break;
}
}
// Copy the next character.
*wStart = *rStart;
// If the character copied was a '\n' mark that state
if (*rStart == '\n')
{
state(tabNeeded) = 0;
}
}
if (rStart != rEnd)
{
res = std::codecvt_base::partial;
}
rNewStart = rStart;
wNewStart = wStart;
return res;
}
// Override so the do_out() virtual function is called.
virtual bool do_always_noconv() const throw()
{
return false; // Sometime we add extra tabs
}
};
See: Tom's notes below
C++ Indenting output class inheriting ofstream
You usually shouldn't inherit std::ostream
or implementations of it like std::ofstream
. Wrap them into another class instead.
Here's a short sketch of my ideas mentioned in the comments
#include <iostream>
#include <fstream>
using namespace std;
class Logger {
public:
Logger(ostream& os) : os_(os), curIndentLevel_(0) {}
void increaseLevel() { ++curIndentLevel_; }
void decreaseLevel() { --curIndentLevel_; }
private:
template<typename T> friend ostream& operator<<(Logger&, T);
ostream& os_;
int curIndentLevel_;
};
template<typename T>
ostream& operator<<(Logger& log, T op) {
for(int i = 0; i < log.curIndentLevel_ * 4; ++i) {
log.os_ << ' ';
}
log.os_ << op;
return log.os_;
}
int main() {
Logger log(cout);
log.increaseLevel();
log << "Hello World!" << endl;
log.decreaseLevel();
log << "Hello World!" << endl;
return 0;
}
Output
Hello World!
Hello World!
Live Sample
Here's a little variant, showing how you can shortcut the coding with operator<<()
overloads:
class Logger {
public:
Logger(ostream& os) : os_(os), curIndentLevel_(0) {}
Logger& increaseLevel() { ++curIndentLevel_; return *this; }
Logger& decreaseLevel() { --curIndentLevel_; return *this; }
// ... as before ...
};
int main() {
Logger log(cout);
log.increaseLevel() << "Hello World!" << endl;
log.decreaseLevel() << "Hello World!" << endl;
return 0;
}
Live Sample
The same way you can go to provide additional I/O manipulator style free functions.
How to add indention to the stream operator
The simplest solution is to slip a filtering streambuf between theostream
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: theLoggingOStreambuf
takes __FILE__
and __LINE__
as arguments, setsmyIndent
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.)
make std::ostream automatically ident when encountering special characters
boost::iostreams makes this fairly easy, you can define filters then chain them together with an output stream to transform the input to the desired output:
#include <iostream>
#include <boost/iostreams/filtering_stream.hpp>
namespace io = boost::iostreams;
struct QuoteOutputFilter {
typedef char char_type;
typedef io::output_filter_tag category;
int indent = 0;
template<typename Sink>
bool newLine(Sink& snk)
{
std::string str = "\n" + std::string(indent * 4, ' ');
return io::write(snk, str.c_str(), str.size());
}
template<typename Sink>
bool put(Sink& snk, char c)
{
switch (c)
{
case '<':
io::put(snk, c);
indent += 1;
return newLine(snk);
case ',':
io::put(snk, c);
return newLine(snk);
case '>':
indent -= 1;
newLine(snk);
return io::put(snk, c);
default:
return io::put(snk, c);
}
}
};
int main()
{
io::filtering_ostream out;
out.push(QuoteOutputFilter());
out.push(std::cout);
out << "test0<test1<test2, test3<test4> > >";
}
c++ custom output stream with indentation
The iostreams support adding custom data to them, so you don't need to write a full derived class just to add an indentation level that will be operated on by manipulators. This is a little-known feature of iostreams, but comes in handy here.
You would write your manipulators like this:
/* Helper function to get a storage index in a stream */
int get_indent_index() {
/* ios_base::xalloc allocates indices for custom-storage locations. These indices are valid for all streams */
static int index = ios_base::xalloc();
return index;
}
ios_base& inc_ind(ios_base& stream) {
/* The iword(index) function gives a reference to the index-th custom storage location as a integer */
stream.iword(get_indent_index())++;
return stream;
}
ios_base& dec_ind(ios_base& stream) {
/* The iword(index) function gives a reference to the index-th custom storage location as a integer */
stream.iword(get_indent_index())--;
return stream;
}
template<class charT, class traits>
basic_ostream<charT, traits>& endl_ind(basic_ostream<charT, traits>& stream) {
int indent = stream.iword(get_indent_index());
stream.put(stream.widen('\n');
while (indent) {
stream.put(stream.widen('\t');
indent--;
}
stream.flush();
return stream;
}
aligning output of ofstream
Yes, <iomanip>
header provides the setw
manipulator, letting you set the width of each field that you output to an ostream
. Using setw
manipulator for each row instead of tab characters would provide tighter control over the output:
output << setw(25) << "Start time part 1 " << timeStr << " on " << dateStr << endl;
output << setw(25) << "Start time part 1000000 " << timeStr << " on " << dateStr << endl;
To align strings on the left, add left
manipulator:
output << left << setw(25) << "Start time part 1 " << timeStr << " on " << dateStr << endl;
output << left << setw(25) << "Start time part 1000000 " << timeStr << " on " << dateStr << endl;
Related Topics
How to Store Variant Data in C++
Dealing with Floating Point Exceptions
How to Specify Vc11 Lambda Calling Convention
What's the Semantically Accurate Position for the Ampersand in C++ References
How to Find Out Cl.Exe's Built-In MACros
Does the Unary + Operator Have Any Practical Use
How to Set Error_Code to Asio::Yield_Context
Duplicate Const Qualifier Allowed in C But Not in C++
Concurrent Writes in the Same Global Memory Location
Clion C++ Can't Read/Open .Txt File in Project Directory
Fast Multiplication/Division by 2 for Floats and Doubles (C/C++)
What Is the Array Form of 'Delete'
Const Method That Modifies *This Without Const_Cast
Get Private Data Members for Non Intrusive Boost Serialization C++