4

Let's say Ì have some class and added output functionality by overloading the left-shift operator:

struct Foo
{
    int i = 1;
    std::string s = "hello";
};

auto& operator<<(std::ostream& os, Foo const& foo)
{
     os<<foo.i<<"\n";
     os<<foo.s<<"\n";
     return os;
}

What is a good way to indent the output?

Example: If I write

std::cout<<"    "<<Foo{}<<std::endl;

the output is:

    1
hello

Obviously, hello is not indented. Is there an easy way to indent the whole output (and not just the first element)?

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • Replace with "os<<"\t"< – wyas May 05 '16 at 21:58
  • 2
    Print to a `stringstream` and post-process the string? I don't know what the post-processing would look like on C++, but it shouldn't be too bad. – user2357112 May 05 '16 at 21:59
  • As @user2357112 pointed out, a decent solution would be to pipe the output to a `stringstream`, then parse the string in there. Then add indentations as necessary and writing to `cout`. – Colin Basnett May 05 '16 at 22:02
  • @ColinBasnett: thanks. My question is a bit underspecified, but I wouldn't consider the `stringstream` processing as "easy" nor "convenient". Moreover, it fails to reproduce the leading whitespace characters of a line ... sure, I can parse the linebreaks, but that's even less convenient ... – davidhigh May 05 '16 at 22:09
  • There's no silver bullet here, you need to perform post processing on data you're writing to the stream given your current set up. That is unless, of course, you want to wade into the worrying world of having global statics that keep track of the current indentation level and then taking that into account in your `<<` operator; but that is bad program design because it makes that code unusable outside the context of this indentation scheme. – Colin Basnett May 05 '16 at 22:17
  • You may want to think about how to achieve this without needing to use the `<<` operator at all. Without more knowledge about the goals and requirements of your program, it's impossible to give any more specific advice than that. – Colin Basnett May 05 '16 at 22:17
  • @wyas: Unfortunately, there is no guarantee that the output will appropriately handle the tab character. I've seen consoles (terminals) that ignore it, to consoles that use variable width fonts and the tab comes out differently. Mind you that some applications replace the tab with a fixed number of spaces, some advance to the next tab stop while others use default tab stops that are multiple of 2, 3, 4 or 8. – Thomas Matthews May 05 '16 at 22:58
  • I suppose you could use something along the lines of http://codereview.stackexchange.com/questions/54845/filtering-streambuf – T.C. May 05 '16 at 23:24
  • ^ That's pretty much what I'd do. – Lightness Races in Orbit May 06 '16 at 01:46
  • Is there some way to read the width flag set by setw() and use that for each member? – Jerry Jeremiah May 06 '16 at 02:18
  • Does this help: http://stackoverflow.com/q/1391746/2069064 ? – Barry May 06 '16 at 14:18

2 Answers2

1

You're serializing the Foo object right? So logically the serialized string of Foo is an implementation detail of Foo. You could write your own stream class or something along those lines but that is overengineering the problem.

auto& operator<<(std::ostream& os, Foo const& foo)
{
    auto s = "\t" + std::to_string(foo.i) + "\n"
             "\t" + foo.s;
    return (os << s);
}

int main()
{
    std::cout << Foo{} << "\n";
}
0

You can use the standard library manipulator setw to set the width of a field, which often results in indenting the text. Here's how you use it:

cout << std::setw(10) << "Viola!" << std::endl;

This will print the word "Viola!" indented by 4 spaces. Why 4 spaces? The parameter to setw() determines the entire width of the "field", which includes the 6 characters in "Viola!".

By default, setw() will align the text to the right, but can be made to align left by using another manipulator left. Here's an example:

cout << std::setw(10) << std::left << "Viola!" << std::endl;

This will output the string "Viola!" with no indentation, but with 4 spaces after it.

That should answer your original question about a good way to indent, and setw() is not just a good way, but the standard way.

The second question asks about how to have persistent indentation, and the answer is that there is not an easy way. The easiest approach is to add the call to setw() (or whichever indentation method that you use) in each of the calls to cout.

In addition to those answers, you should consider replacing the use of "\n" in your calls to cout with a call to endl. endl is the "end of line" manipulator, and makes your code work properly with any output stream. The code would look like this:

auto& operator<<(std::ostream& os, Foo const& foo)
{
     os << foo.i << std::endl;
     os << foo.s << std::endl;
     return os;
}
Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43
  • thanks. my question was exactly about the "persistent indentation" part. With regards to replacing the `\n`'s by `std::endl`: I guess that will in general give a worse performance, because it implies a `flush` (which might actually not be needed). – davidhigh May 06 '16 at 14:15