C++ Output Formatting: setw, setprecision and std::format
Format C++ output with iomanip manipulators or C++20 std::format: width, alignment, fill, fixed decimals and hex. Which settings stick, and aligning a table.
- Course: C++ study plan
- Module: Strings
- Kind: Lesson
- Reading time: 14 min
- Runtime: Clang 18 · C++20
How do you format output in C++?
C++ formats output in two ways. The <iomanip> stream manipulators — std::setw, std::left, std::setfill, std::fixed, std::setprecision — change how the following values print, and all but setw stay in force until changed. C++20's std::format("{:>8.2f}", x) puts width, alignment and precision in one placeholder, returns a std::string, and leaves nothing sticky.
Lesson
Output that must match a judge or line up in a report is a formatting problem before anything else: how wide is the column, which side is padded, how many decimals, zero-filled or hex. C++ has two toolkits. The stream manipulators in <iomanip> have been there from the start and are what most existing code uses; C++20's std::format says the same things in one compact specification and is the one for new code. This lesson settles both — including which stream settings are sticky and which apply once, the mistake that bites everyone — and shows how to build a table, and a string, before printing anything.
Width, alignment and fill
#include <iomanip>
#include <iostream>
int main() {
std::cout << '[' << std::setw(6) << 42 << "]\n"; // [ 42]
std::cout << '[' << std::setw(6) << std::left << 42 << "]\n"; // [42 ]
std::cout << '[' << std::setw(6) << "ab" << "]\n"; // [ab ] - left is still on
std::cout << std::right << std::setfill('0') << std::setw(5) << 7 << '\n'; // 00007
std::cout << std::setw(3) << 1 << std::setw(3) << 2 << '\n'; // 001002 - fill is still '0'
return 0;
}
std::setw(n) sets the minimum width of the next insertion only; the one after it is back to width 0. std::left, std::right and std::setfill(c) stay in force until changed, which is why the last line above zero-fills both numbers. Numbers and strings alike default to right alignment on a stream. A value wider than the field is never truncated; the field grows.
Precision and the floating-point modes
double x = 3.14159265;
std::cout << std::setprecision(3) << x << '\n'; // 3.14 - 3 SIGNIFICANT digits
std::cout << std::setprecision(3) << 1234.5678 << '\n'; // 1.23e+03 - still 3 significant digits
std::cout << std::fixed << std::setprecision(3) << x << '\n'; // 3.142 - 3 digits AFTER the point
std::cout << std::fixed << std::setprecision(2) << 2.0 / 3 << '\n'; // 0.67
std::cout << std::scientific << x << '\n'; // 3.14e+00 - precision 2 still applies
std::cout << std::defaultfloat << std::setprecision(6) << x << '\n'; // 3.14159 - back to the default
Without std::fixed, setprecision counts significant digits and the stream may switch to scientific notation for large or small values — hence 1.2e+03 for 1234.5 at setprecision(2). With std::fixed it counts digits after the point, the mode every exercise with money or averages wants. Both are sticky; set them once at the top of main. Rounding is to nearest, so 2.0 / 3 at two decimals is 0.67, while an exact tie such as 0.125 rounds to even and prints 0.12 — never build an expectation on a tie.
Integers have their own modes: std::hex, std::oct and std::dec change the base and stay changed, std::showbase adds 0x, std::uppercase capitalises the digits. Forgetting to switch back to std::dec is a classic: every integer printed afterwards comes out in hex.
Sticky or one-shot
| Manipulator | Effect | Lasts |
|---|---|---|
std::setw(n) | minimum width | next insertion only |
std::left / std::right / std::internal | alignment within the width | until changed |
std::setfill(c) | padding character | until changed |
std::fixed / std::scientific / std::defaultfloat | floating-point mode | until changed |
std::setprecision(n) | digits (meaning depends on the mode) | until changed |
std::hex / std::oct / std::dec | integer base | until changed |
std::boolalpha | true/false instead of 1/0 | until changed |
std::format
#include <format>
std::cout << std::format("{} scored {}\n", "Ada", 97); // Ada scored 97
std::cout << std::format("[{:>6}] [{:<6}] [{:^6}]\n", 42, 42, 42); // [ 42] [42 ] [ 42 ]
std::cout << std::format("[{:6}] [{:6}]\n", 42, "ab"); // [ 42] [ab ] - numbers right, text left
std::cout << std::format("{:05} {:+d} {:.3f} {:8.2f}\n", 42, 7, 3.14159, 2.5); // 00042 +7 3.142 2.50
std::cout << std::format("{:x} {:X} {:#x} {:08b} {:o}\n", 255, 255, 255, 5, 8); // ff FF 0xff 00000101 10
std::cout << std::format("{1} before {0}\n", "b", "a"); // a before b
std::cout << std::format("{:*^11}\n", "mid"); // ****mid****
Each {} is replaced by the next argument; after a colon comes a specification in a fixed order: fill and align (< left, > right, ^ centre, optionally preceded by the fill character), sign (+), # for a base prefix, 0 for zero padding, width, .precision, and a type letter — d, x, X, b, o for integers, f, e, g for floating point, s for strings. Everything is optional; {:8.2f} reads "width 8, two decimals, fixed". Unlike streams, nothing is sticky: each placeholder says everything about itself, and the default alignment differs by kind — numbers right, text left, which is exactly what a table wants.
The format string must be known at compile time, because the compiler checks the placeholders against the argument types: {:d} with a std::string is a compile error, not a runtime surprise. For a string built at run time use std::vformat(fmt, std::make_format_args(args...)). std::format returns a std::string; std::print (C++23) writes straight to stdout but is not available on this runtime — write std::cout << std::format(...).
Building a string first
#include <sstream>
std::ostringstream out;
out << std::fixed << std::setprecision(1);
for (int i = 1; i <= 3; ++i) out << i << ':' << i * 0.5 << ' ';
std::string text = out.str(); // "1:0.5 2:1.0 3:1.5 "
std::cout << text << '\n';
An std::ostringstream accepts everything std::cout does and hands the result back with str(): assemble a line before deciding whether to print it, measure a string's width before padding, or collect a large output and write it once. std::format covers the simple cases since it already returns a string; the stream wins when many pieces with shared settings accumulate in a loop.
A table, done right
struct Row { std::string name; int qty; double price; };
void print_table(const std::vector<Row>& rows) {
std::cout << std::format("{:<10}{:>5}{:>10}\n", "item", "qty", "price");
for (const Row& r : rows) {
std::cout << std::format("{:<10}{:>5}{:>10.2f}\n", r.name, r.qty, r.price);
}
}
item qty price
pen 3 1.20
notebook 12 45.00
The rules that keep a table clean and a judge happy: right-align numbers so decimal points line up; put the left-aligned text column first so no line ends in padding; give each column a width that fits its widest value and use the same widths for the header. This judge ignores trailing spaces, but a left-aligned last column still produces them and other judges compare bytes — end each row with a right-aligned column and the problem never arises.
Pitfalls
setwis one-shot;setprecision,fixed,left,setfillandhexare sticky and stay on for the rest of the program.setprecision(2)withoutfixedmeans two significant digits:1234.5prints as1.2e+03.std::hexleft on turns every later integer into hex; switch back withstd::dec.- The
std::formatstring must be a compile-time constant; astd::stringvariable does not compile — usestd::vformat. - A left-aligned last column pads with spaces to the end of the line; put text columns first.
- Ties such as
0.125round to even at two decimals; do not build an expectation on one.
Key takeaways
- Streams:
setwapplies once; alignment, fill, precision, floating mode and base persist. std::fixedplussetprecision(n)is the deterministic way to printndecimals; withoutfixed,ncounts significant digits.std::format("{:>8.2f}")reads fill/align, sign,#,0, width, precision, type — nothing is sticky, numbers right-align and text left-aligns by default.- The format string is checked at compile time; it must be a literal.
std::ostringstreamcollects output into a string viastr();std::formatreturns one directly.- Put text columns first and right-align numbers so no line ends in padding.
Common questions
What does setw do in C++?
std::setw(n), from <iomanip>, sets the minimum width of the next value inserted into a stream, padding it with the fill character; the value after it is back to width 0. A value wider than the field is never truncated. Alignment comes from std::left or std::right, which stay in force.
What is the difference between setprecision with and without fixed?
Without std::fixed, std::setprecision(n) counts significant digits and may switch to scientific notation, so 1234.5 at precision 2 prints 1.2e+03. With std::fixed it counts digits after the decimal point, so std::fixed << std::setprecision(2) prints exactly two decimals. Both settings are sticky.
How does std::format work in C++?
std::format (C++20, <format>) replaces each {} in a compile-time format string with the next argument and returns a std::string. After a colon a placeholder can set fill and alignment, sign, width, precision and type: {:>8.2f} is right-aligned, width 8, two decimals. Nothing is sticky, and mismatched types are compile errors.
Which iomanip manipulators are sticky?
Only std::setw is one-shot, applying to the next insertion alone. std::left, std::right, std::setfill, std::fixed, std::scientific, std::setprecision, std::hex, std::dec and std::boolalpha stay in force until changed — so a forgotten std::hex prints every later integer in hexadecimal.
How do you print an aligned table in C++?
Give each column a fixed width that fits its widest value, left-align text and right-align numbers so decimal points line up — for example std::format("{:<10}{:>5}{:>10.2f}", name, qty, price). Put text columns first and end each row with a right-aligned column, so no line ends in padding.
Exercises
Receipt
Read an integer N and then N lines, each name qty price — a one-word item name, an integer quantity and a decimal unit price. Print one line per item and a total line, with exact widths:
- item lines: name left-aligned in width 12, quantity right-aligned in width 4, line total (
qty × price) right-aligned in width 10 with two decimals — thestd::formatspec is{:<12}{:>4}{:>10.2f}; - the last line:
TOTALleft-aligned in width 16, then the grand total right-aligned in width 10 with two decimals.
A name longer than 12 characters simply pushes its columns right. N may be 0, in which case only the total line is printed.
Input: N, then N lines. Output: N + 1 lines.
Example: input
3
pen 3 1.25
notebook 2 4.5
stapler 1 12.99
→ output
pen 3 3.75
notebook 2 9.00
stapler 1 12.99
TOTAL 25.74Number bases
Read integers in the range 0 to 65535 until end of input (whitespace-separated). Print a header line and then one row per number showing it in four bases, columns separated by |:
- decimal right-aligned in width 5;
- hexadecimal in lower case, zero-padded to 4 digits;
- octal right-aligned in width 6;
- binary zero-padded to 16 digits.
The header uses the same widths, right-aligned: {:>5}|{:>4}|{:>6}|{:>16} with the words dec, hex, oct, bin; each row is {:>5}|{:04x}|{:>6o}|{:016b}. std::format makes the binary column trivial — with <iomanip> you would have to build it by hand. With no numbers at all, only the header is printed.
Input: zero or more integers. Output: the header, then one line per integer.
Example: input 255 0 65535 → output
dec| hex| oct| bin
255|00ff| 377|0000000011111111
0|0000| 0|0000000000000000
65535|ffff|177777|1111111111111111