Search code examples
c++multiple-columnscoutsetw

C++ setup columns using cout


So i'm just starting to learn c++ and i'm curious if its a way to formate your output with cout so it will look nicely and structured in columns

for example.

string fname = "testname";
    string lname = "123";
    double height = 1.6;

    string fname2 = "short";
    string lname2 = "123";
    double height2 = 1.8;

    cout << "Name" << setw(30) << "Height[m]" << endl;
    cout << fname + " " + lname << right << setw(20) << setprecision(2) << fixed << height << endl;
    cout << fname2 + " " + lname2 << right << setw(20) << setprecision(2) << fixed << height2 << endl

The output looks like this:

 Name                   Height[m]
 testname 123              1.60
 short 123              1.80

I want it to look like this:

Name                   Height[m]
testname 123             1.60
short 123                1.80

The problem i'm trying to solve is that i want to place height at a specific position from name but depending what length of name i take the value of height either gets far away to the right or will be very close to the left. Is there a way to fix this?


Solution

  • First of all, with an output stream like std::cout, you cannot travel back in time and modify output which was already performed. That makes sense -- just imagine std::cout wrote into a file because you launched your program with program.exe > test.txt, and test.txt was on a USB drive which has been disconnected in the meanwhile...

    So you have to get it right immediately.

    Basically, there are two ways to do so.

    You can assume that no entry in the first column will ever be wider than a certain number of characters, which is what you have attempted. The problem is that your setw is at the wrong position and that right should be left. A stream manipulator must be placed before the elements which should be affected. And since you want left-aligned columns, you need left:

    cout << left << setw(20) << "Name" << "Height[m]" << endl;
    cout << left << setw(20) << fname + " " + lname << setprecision(2) << fixed << height << endl;
    cout << left << setw(20) << fname2 + " " + lname2 << setprecision(2) << fixed << height2 << endl;
    

    But this solution is not very general. What if you'll have a name with 21 characters? Or with 30 characters? Or 100 characters? What you really want is a solution in which the column is automatically set only as wide as necessary.

    The only way to do this is to collect all entries before printing them, finding the longest one, setting the column width accordingly and only then print everything.

    Here is one possible implementation of this idea:

    std::vector<std::string> const first_column_entries
    {
        "Name",
        fname + " " + lname,
        fname2 + " " + lname2
    };
    
    auto const width_of_longest_entry = std::max_element(std::begin(first_column_entries), std::end(first_column_entries),
        [](std::string const& lhs, std::string const& rhs)
        {
            return lhs.size() < rhs.size();
        }
    )->size();
    
    // some margin:
    auto const column_width = width_of_longest_entry + 3;
    
    std::cout << std::left << std::setw(column_width) << "Name" << "Height[m]" << "\n";
    std::cout << std::left << std::setw(column_width) << fname + " " + lname << std::setprecision(2) << std::fixed << height << "\n";
    std::cout << std::left << std::setw(column_width) << fname2 + " " + lname2 << std::setprecision(2) << std::fixed << height2 << "\n";
    

    The next step of evolution would be generalising the std::vector into a self-written class called Table and iterating that Table's rows in a loop in order to print the entries.