Embercode
✕
C++ PatternsLesson 13 of 14

40 min read

Aligned tables

Why this matters

A table is easy to read when the numbers in each column line up. A list of prices with the decimal points in different places is hard to scan. A multiplication table where the products shift by one character on every row looks wrong even when every number is right.

Programs print tables all the time: reports, receipts, test results and logs. Each row of a table is one line of output, and each column must start at the same position on every line. The C++ tool for this is std::setw. It sets the minimum width of the next value that is printed. This lesson shows how to use it, how to choose the widths from the data, and what goes wrong when the widths are wrong.

The previous lessons in this module drew shapes and zigzags out of stars and spaces. This lesson prints numbers and text in columns, and it does not repeat those shape techniques.

This lesson uses C++ with the standard output stream std::cout. Compile the samples with g++ -std=c++20 -Wall. The output shown under each sample was produced by running the sample on g++ 15.2.

The idea: a width for each value

Start with three numbers: 3, 12 and 144. Printed with std::cout << a << b << c, they run together as 312144. Nothing separates them, so the reader cannot tell where one number ends.

std::setw fixes this. It is declared in the <iomanip> header. The name stands for "set width". Put it in front of a value, and the value is printed in a field at least that many characters wide. A field is a fixed-size block of space on the line. The value is pushed to the right side of the field by default, which is called right alignment.

#include <iomanip>
#include <iostream>
 
int main() {
    int a = 3;
    int b = 12;
    int c = 144;
 
    // Each setw(5) pads only the value that follows it.
    std::cout << std::setw(5) << a << std::setw(5) << b << std::setw(5) << c << '\n';
 
    // Without setw, the same values print with no padding.
    std::cout << a << b << c << '\n';
 
    // setw applies to one output only. The 8 does not get a width.
    std::cout << std::setw(4) << 7 << 8 << '\n';
 
    // Text is right-aligned by default, the same as numbers.
    std::cout << std::setw(6) << "name" << "|\n";
 
    // std::left is sticky: it stays on until something changes it.
    std::cout << std::left << std::setw(6) << "name" << "|\n";
    std::cout << std::setw(6) << "ab" << "|\n";
    std::cout << std::right << std::setw(6) << "cd" << "|\n";
 
    // setw is a minimum. A value wider than the width is printed in full.
    std::cout << std::setw(2) << 12345 << "|\n";
}

The output is:

    3   12  144
312144
   78
  name|
name  |
ab    |
    cd|
12345|

Look at the first line. Each number ends in the same place as the number before it, because each one has a field five characters wide. The number 3 has four spaces before it, and 144 has two.

The second line has no setw, so the values run together. This is the baseline that shows what setw changes.

The third line shows the most common surprise. std::setw(4) applies only to the value right after it, which is 7. The 8 that follows has no width of its own, so it is printed with no padding. The result is 78, with three spaces before the 7 and none before the 8.

The fourth line shows that text is right-aligned by default. The word name is pushed to the right side of its six-character field. The fifth to seventh lines use std::left, which makes later fields left-aligned. std::left stays on until something changes it. The std::right on the last of these lines switches back.

The last line shows that setw is a minimum. The field is only two characters wide, but the value 12345 has five digits. The output does not cut the number down. It prints the whole number, and the line is longer than the field.

Why the width must be set on every value

A table needs setw in front of every value on every line. A common beginner error is to set the width for the first column only and then print the rest of the row without it. The row starts correctly, then the rest of the row drifts out of line.

Each of the samples in this lesson sets the width before each value.

Worked example: a multiplication table

The first table is a multiplication table from 1 to 10. The column width must be wide enough for the largest number in the table, which is 10 times 10, or 100. The number 100 has three digits. Each column should be three digits wide with a gap of one or two spaces on the left.

The width depends on the data, so the program computes it. A helper function counts the digits of a number. The width is the digit count of the largest product plus two.

#include <iomanip>
#include <iostream>
#include <string>
 
// Counts the decimal digits in a non-negative number. 0 has one digit.
int count_digits(int n) {
    int digits = 1;
    while (n >= 10) {
        n /= 10;
        ++digits;
    }
    return digits;
}
 
int main() {
    const int n = 10;
 
    // The widest value in the table is n * n. Add two spaces of gap.
    const int width = count_digits(n * n) + 2;
 
    // Header row: the corner label, then the column numbers.
    std::cout << std::setw(width) << 'x';
    for (int col = 1; col <= n; ++col) {
        std::cout << std::setw(width) << col;
    }
    std::cout << '\n';
 
    // A line under the header, as wide as the header.
    std::cout << std::string(width * (n + 1), '-') << '\n';
 
    // One line per row. Each product is padded to the same width.
    for (int row = 1; row <= n; ++row) {
        std::cout << std::setw(width) << row;
        for (int col = 1; col <= n; ++col) {
            std::cout << std::setw(width) << row * col;
        }
        std::cout << '\n';
    }
}

The output is:

    x    1    2    3    4    5    6    7    8    9   10
-------------------------------------------------------
    1    1    2    3    4    5    6    7    8    9   10
    2    2    4    6    8   10   12   14   16   18   20
    3    3    6    9   12   15   18   21   24   27   30
    4    4    8   12   16   20   24   28   32   36   40
    5    5   10   15   20   25   30   35   40   45   50
    6    6   12   18   24   30   36   42   48   54   60
    7    7   14   21   28   35   42   49   56   63   70
    8    8   16   24   32   40   48   56   64   72   80
    9    9   18   27   36   45   54   63   72   81   90
   10   10   20   30   40   50   60   70   80   90  100

The width is 5. That is 3 for the digits of 100, plus 2 for the gap. Every column is 5 characters wide, so the table has 11 fields of width 5, which is 55 characters. The dashed line is width * (n + 1) characters long, which is 55, so it matches the header.

The row label and the column numbers use the same width. This keeps the first column of the table in the same position as the header. A table that uses different widths for the labels and the values looks uneven.

The function count_digits shows the loop pattern. Dividing an integer by 10 removes its last digit. The integer division truncates the result, so 100 / 10 is 10, and 10 / 10 is 1. Each pass counts one more digit. The loop stops when the number is below 10.

Change n to 12 and the width becomes 5 still, because 144 also has three digits. Change n to 3 and the width becomes 3, because 9 has one digit and the gap makes it 3. The table adjusts on its own, and this is why the width is computed and not fixed.

Worked example: a price list with mixed alignment

Tables often mix text and numbers. Names look best left-aligned, so the eye can follow them down the column. Numbers look best right-aligned, so the decimal points line up. This sample uses both. It also prints prices with two decimal places.

The price list is a std::vector of a small struct. A struct is a type that groups several named values. std::vector is a list that can grow, and its loop uses a range-based for.

#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
 
struct Item {
    std::string name;
    int quantity;
    double unit_price;
};
 
int main() {
    const std::vector<Item> items = {
        {"Pencil", 12, 0.5},
        {"Notebook", 3, 2.25},
        {"Stapler", 1, 14.0},
        {"Glue stick", 100, 0.75},
    };
 
    const int name_width = 12;
    const int qty_width = 5;
    const int price_width = 9;
    const int total_width = name_width + qty_width + price_width * 2;
 
    // Names are left-aligned. Numbers are right-aligned.
    std::cout << std::left << std::setw(name_width) << "Item"
              << std::right << std::setw(qty_width) << "Qty"
              << std::setw(price_width) << "Price"
              << std::setw(price_width) << "Total" << '\n';
    std::cout << std::string(total_width, '-') << '\n';
 
    double grand_total = 0.0;
    for (const Item& item : items) {
        double total = item.quantity * item.unit_price;
        grand_total += total;
 
        std::cout << std::left << std::setw(name_width) << item.name
                  << std::right << std::setw(qty_width) << item.quantity
                  << std::fixed << std::setprecision(2)
                  << std::setw(price_width) << item.unit_price
                  << std::setw(price_width) << total << '\n';
    }
 
    std::cout << std::string(total_width, '-') << '\n';
    std::cout << std::left << std::setw(name_width + qty_width + price_width) << "Grand total"
              << std::right << std::setw(price_width) << grand_total << '\n';
}

The output is:

Item          Qty    Price    Total
-----------------------------------
Pencil         12     0.50     6.00
Notebook        3     2.25     6.75
Stapler         1    14.00    14.00
Glue stick    100     0.75    75.00
-----------------------------------
Grand total                  101.75

The program switches between std::left and std::right for each column. The name column uses std::left, so the names start at the same place. The number columns use std::right, so the last digits line up.

The line std::fixed << std::setprecision(2) has two effects. std::fixed makes every floating-point number print with a fixed number of digits after the decimal point. std::setprecision(2) sets that number to two. Without these, the number 14.0 would print as 14 and 0.5 would print as 0.5. The prices in the table would then have different numbers of digits and would not line up. Both settings stay on for the rest of the program, which is why the total column also prints with two decimal places.

The total width of the table is a sum of the field widths. The line of dashes uses the same sum, so the rule is always as wide as the table. When the widths change, the rule changes with them if the code computes it from the same constants.

The last line of the table is a label that spans the name, quantity and price columns. Its width is the sum of those three widths, so the grand total stays under the Total column.

Check the totals by hand. Twelve pencils at 0.50 cost 6.00. Three notebooks at 2.25 cost 6.75. One stapler at 14.00 costs 14.00. One hundred glue sticks at 0.75 cost 75.00. The sum is 101.75, which matches the last line.

Common mistakes

Most table mistakes come from setting a width in one place and forgetting it in another, or from a width that is too small for the data. Each of the samples below was compiled and run.

Mistake 1: setw applies to the whole expression that follows it

std::setw applies to the value that follows it. If the value is built from several parts, the width applies to the whole result, not to one part. A common version of this error is to put a label and a value next to each other, with the width set for the label only.

#include <iomanip>
#include <iostream>
 
int main() {
    std::cout << std::setw(12) << "Name:" << "Asha|\n";
}

The output is:

       Name:Asha|

The label is padded to 12 characters. The value Asha has no width, so it starts right after the colon. The line looks like one field, but it is two fields with only one of them set. The fix is to build the whole text first, then give it a width:

std::string label = std::string("Name: ") + "Asha";
std::cout << std::setw(12) << label << "|\n";

The output of that version is Name: Asha|. The width now applies to the complete text.

Mistake 2: a double without fixed prints a different number of digits

A double printed with default settings shows only the digits it needs. The value 2.5 prints as 2.5, and 2.50 prints the same way. The two prices in a column will have different lengths, and the decimal points will not line up.

#include <iomanip>
#include <iostream>
 
int main() {
    double price = 2.5;
    std::cout << std::setw(8) << price << "|\n";
    std::cout << std::setw(8) << std::fixed << std::setprecision(2) << price << "|\n";
}

The output is:

     2.5|
    2.50|

The first line uses the default format. The second line sets std::fixed and std::setprecision(2) before the value, which gives two digits after the decimal point. Both lines are eight characters wide, but only the second one has the digits a price needs.

Mistake 3: forgetting the header

Without <iomanip>, the compiler does not know the name setw. The error appears at the first line that uses it. This sample is the smallest program that shows the error:

#include <iostream>
 
int main() {
    std::cout << std::setw(5) << 42 << '\n';
}

The compiler prints this error, and the program is not built. The first lines of the real output are (the file name is the one you saved the file as):

no_iomanip.cpp: In function ‘int main()’:
no_iomanip.cpp:4:23: error: ‘setw’ is not a member of ‘std’
    4 |     std::cout << std::setw(5) << 42 << '\n';
      |                       ^~~~
no_iomanip.cpp:2:1: note: ‘std::setw’ is defined in header ‘<iomanip>’; this is probably fixable by adding ‘#include <iomanip>’

The note on the last line says what to add. The fix is the line #include <iomanip> at the top of the file.

Mistake 4: a width that is too small

A width is a minimum. When a value has more characters than the width, the value is printed in full, and the columns after it move to the right. In a table this looks like a broken line. The sample in the basics section printed 12345 in a field of width 2 and the line became longer than the others.

The fix is to compute the width from the data, as the multiplication table does. Find the widest value in the column, count its characters, and use that count plus a gap.

Practice

Exercise 1. Change the multiplication table so n is 12. Predict the width before you run the program. Then state what the header line starts with.

Show the answer

The largest product is 144, which has three digits. The width is 3 plus 2, which is 5, the same as before. The header line starts with four spaces and the letter x, then the numbers 1 to 12, each in a field of 5 characters. The output has a dashed line of 65 characters, because 5 * 13 is 65.

Exercise 2. Write a table of squares and cubes for the numbers 1 to 5. The first column is 4 wide and left-aligned. The squares column is 6 wide, and the cubes column is 8 wide. The header reads n, square and cube. Use std::setw for every value.

Show the answer

#include <iomanip>
#include <iostream>
 
int main() {
    std::cout << std::setw(4) << std::left << "n"
              << std::right << std::setw(6) << "square"
              << std::setw(8) << "cube" << '\n';
    for (int n = 1; n <= 5; ++n) {
        std::cout << std::setw(4) << n
                  << std::setw(6) << n * n
                  << std::setw(8) << n * n * n << '\n';
    }
}

The output is:

n   square    cube
   1     1       1
   2     4       8
   3     9      27
   4    16      64
   5    25     125

The first column is left-aligned, so the n sits at the left edge of its field. The std::right after it switches the other columns back to right alignment.

Exercise 3. Predict what this line prints, then check the answer by running it.

std::cout << std::setfill('.') << std::left << std::setw(10) << "Pencil" << std::setfill(' ') << "|\n";

Show the answer

The output is Pencil....|. The std::setfill('.') changes the padding character from a space to a dot. The text Pencil is 6 characters and the field is 10, so 4 dots are added on the right because of std::left. The std::setfill(' ') changes the padding back to a space for the rest of the program.

Exercise 4. Explain why the price list uses std::fixed and std::setprecision(2) on the price and total values but not on the quantity.

Show the answer

The quantity is an int with no decimal point, so it always prints in full, like 12 or 100. The price and the total are double values, which print with a varying number of digits by default. Fixing two digits after the decimal point makes every price the same length, so the decimal points line up in the column.

Exercise 5. A program prints a table. The first three rows line up with the header. Every row after the third is one character further to the right. Name one cause that fits this pattern, and say how to find it.

Show the answer

One cause is an extra character printed between the rows, such as a stray std::cout << ' ' that runs only after row three, which shifts everything after it. Another is a width that changes partway through, such as a std::setw call with a different number inside the loop after the third row. To find it, read the code between the first and fourth rows and list every output statement in order. A stray character or a changed width shows up in that list.

Summary

  • std::setw(n) sets the minimum width of the next value only. Set it before every value in a row.
  • std::left and std::right stay on until they are changed. Use std::left for names and std::right for numbers.
  • Compute the column width from the data, for example from the digit count of the largest value plus a gap.
  • Use std::fixed and std::setprecision so that decimal values have the same number of digits in a column.
  • Include <iomanip> for std::setw, std::setprecision and the other manipulators.
Check yourself: Zigzag and tables (7 questions) →Practice this topic: coding problems for C++ Patterns →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Zigzag and wave