I often want to create nested indentation in my output. Maybe I’m outputing informatoin regarding a data structure, and I’d like the indentation level to reflect the structure of the data. Eg:
Foo bar1 bar2 bar3
But if Foo was in Boo, it should look like this:
Boo Foo bar1 bar2 bar3
I’ve cobbled together various solutions to this, but I thought it should be possible to create iostream manipulators, or use a facet, or something like that. It seems a lot of other people face this isssue as well:
- http://stackoverflow.com/questions/25216993/c-indenting-output-class-inheriting-ofstream
- http://stackoverflow.com/questions/28271898/mutable-flags-on-a-stdfacet-object
- http://stackoverflow.com/questions/799599/c-custom-stream-manipulator-that-changes-next-item-on-stream/799877#799877
- http://stackoverflow.com/questions/1391746/how-to-easily-indent-output-to-ofstream
I’d like to type:
/// This probably has to be called once for every program:
// http://stackoverflow.com/questions/26387054/how-can-i-use-stdimbue-to-set-the-locale-for-stdwcout
std::ios_base::sync_with_stdio(false);
std::cout << "I want to push indentation levels:\n" << indent_manip::push
<< "To arbitrary depths\n" << indent_manip::push
<< "and pop them\n" << indent_manip::pop
<< "back down\n" << indent_manip::pop
<< "like this.\n"
and get:
I want to push indentation levels: To arbitrary depths and pop them back down like this.
Well, I accomplished it. The above code runs. For a more complicated demo/example/tutorial, see the file indent_test.cpp in this repository.
I suspect there is some pretty serious evil hidden in the current implementation though. I could do a clean implementation by creating a custom ostream operator (and that’s the next project), but I wanted to have a way to modify existing streams (e.g. cout) so’s I could manipulate the operator and affect other libraries and that use existing streams stream. I accomplished that, but the question is did I do something horrible to accomplish my goal?
The problem is:
- I need to call a facet that is called on every string, which
afaik means I have to use codecvt.
- I need the indentation level to persist, which means using iosbase::iword.
- codecvt doesn’t have a reference to iosbase anywhere.
Combine all that with the fact that facets have to be constant objects, and you get a kind of tricky problem. I solved it with a dirty trick:
- I use iword to keep track of the indentation level, since I get stream tracking for free that way. If I was only dealing with a single stream it would useless iformation.
- Whenever indent_manip::push is called, the iword is increment, a new fact is created, a locale is created with that facet, and the current stream is imbued with that locale.
This probably means the implementation is pretty inefficient, but if the indentation level is zero and you don’t call any pushes, you should get your usual performance. Real penalties probably occur only with pushes.
I will experiment with caching the locales at a later date to see if that helps with the performance impact, but I’m more interested in hearing if anyone more knowledgeable sees any safety issues with the current implementation.
- line wrapping
- benchmarking.
- optimization.
- A custom streambuf instead?
- Any ideas?