Skip to content
C++
Library
since C++98
Basic

<iomanip>

Parameterized stream manipulators for field width, fill character, numeric base, floating-point precision, monetary formatting, and date/time I/O.

<iomanip>since C++98

The <iomanip> header provides parameterized stream manipulators β€” callable objects that modify a stream's formatting state when inserted into or extracted from it, distinguished from the no-argument manipulators (std::left, std::fixed, std::hex, etc.) defined in <ios> and exposed by <iostream>.

Overview

Stream manipulators fall into two families. Non-parameterized manipulators live in <ios> (transitively included by <iostream>) and need no arguments: std::left, std::fixed, std::hex, std::boolalpha, and so on. Parameterized manipulators require <iomanip> and take one or more arguments that configure the effect.

All manipulators are sticky except std::setw. Sticky means the formatting change persists for every subsequent I/O operation on that stream until explicitly reset. std::setw is consumed by exactly one insertion or extraction and must be reissued before each field that needs non-default width. This asymmetry is the root of the most common <iomanip> bugs.

Manipulator inventory

ManipulatorSinceStickyDescription
setw(n)C++98NoMinimum field width
setprecision(n)C++98YesFloating-point significant digits or decimal places
setfill(c)C++98YesPad character (default ' ')
setbase(b)C++98YesNumeric base: 8, 10, or 16
setiosflags(mask)C++98YesOR-sets named format flags
resetiosflags(mask)C++98YesClears named format flags
put_money(mon [, intl])C++11β€”Inserts monetary amount using stream locale
get_money(mon [, intl])C++11β€”Extracts monetary amount using stream locale
put_time(tm, fmt)C++11β€”Inserts std::tm as formatted date/time
get_time(tm, fmt)C++11β€”Parses std::tm from formatted date/time
quoted(s [, del [, esc]])C++14β€”Round-trips quoted strings through streams

Syntax

cpp
#include <iomanip>

Manipulators are used inline with << for output streams and >> for input streams:

cpp
std::cout << std::setw(10) << std::setprecision(4) << std::fixed << value;
std::cin  >> std::get_time(&t, "%Y-%m-%d");  // C++11

Examples

Aligned table output

setw controls column width; setfill fills empty space; setprecision combined with std::fixed (from <ios>) controls decimal places:

cpp
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

int main() {
    const std::vector<std::pair<std::string, double>> rows = {
        {"Widget",     1.50},
        {"Gadget",   199.99},
        {"Doohickey",  0.75},
    };

    std::cout << std::left  << std::setw(14) << "Item"
              << std::right << std::setw(10) << "Price" << '\n'
              << std::setfill('-') << std::setw(24) << "" << '\n'
              << std::setfill(' ');                         // reset fill

    std::cout << std::fixed << std::setprecision(2);       // sticky

    for (auto& [name, price] : rows) {                     // C++17
        std::cout << std::left  << std::setw(14) << name
                  << std::right << std::setw(10) << price << '\n';
    }
}
// Item              Price
// ------------------------
// Widget              1.50
// Gadget            199.99
// Doohickey           0.75

Hexadecimal memory dump

setbase (or std::hex from <ios>) plus setw/setfill produce compact binary-inspection output:

cpp
#include <iomanip>
#include <iostream>
#include <cstddef>
#include <span>    // C++20

void hex_dump(std::span<const std::byte> data, std::ostream& out) {  // C++20
    constexpr std::size_t cols = 16;
    const auto saved_flags = out.flags();
    const auto saved_fill  = out.fill();

    out << std::hex << std::setfill('0') << std::uppercase;

    for (std::size_t i = 0; i < data.size(); i += cols) {
        out << std::setw(8) << i << "  ";
        for (std::size_t j = 0; j < cols; ++j) {
            if (i + j < data.size())
                out << std::setw(2)
                    << static_cast<unsigned>(data[i + j]) << ' ';
            else
                out << "   ";
        }
        out << '\n';
    }

    out.flags(saved_flags);
    out.fill(saved_fill);
}

Monetary output (C++11)

put_money delegates to the std::money_put facet of the stream's imbued locale. The amount must be expressed in the locale's minor currency unit (cents for USD):

cpp
#include <iomanip>
#include <iostream>
#include <locale>

int main() {
    std::cout.imbue(std::locale("en_US.UTF-8"));

    const long double amount = 12345.67L;

    std::cout << std::showbase
              << std::put_money(amount * 100)       // C++11 β€” cents
              << '\n';                               // $12,345.67

    std::cout << std::put_money(amount * 100, true) // C++11 β€” international
              << '\n';                               // USD 12,345.67
}

Date/time I/O (C++11)

put_time and get_time wrap std::strftime/std::strptime via the std::time_put/std::time_get locale facets:

cpp
#include <iomanip>
#include <iostream>
#include <sstream>
#include <ctime>

int main() {
    // Write
    std::time_t now   = std::time(nullptr);
    std::tm*    local = std::localtime(&now);
    std::cout << std::put_time(local, "%Y-%m-%d %H:%M:%S") << '\n'; // C++11

    // Read β€” always value-initialize; get_time may leave unset fields undefined
    std::tm parsed{};
    std::istringstream ss("2026-06-15 09:30:00");
    ss >> std::get_time(&parsed, "%Y-%m-%d %H:%M:%S");               // C++11
    if (ss.fail())
        std::cerr << "Parse error\n";
}

Quoted strings (C++14)

std::quoted inserts a delimiter (default ") and escapes any occurrences of the delimiter or escape character inside the string. On extraction it strips the delimiters and unescapes:

cpp
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

int main() {
    const std::string path = R"(C:\Program Files\My "App")";

    std::ostringstream out;
    out << std::quoted(path);           // C++14
    std::cout << out.str() << '\n';
    // "C:\\Program Files\\My \"App\""

    std::string recovered;
    std::istringstream in(out.str());
    in >> std::quoted(recovered);       // C++14
    // recovered == path β€” exact round-trip
}

std::quoted is particularly useful for serialising paths or tokens that may contain spaces, since unquoted operator>> stops at whitespace.

Best Practices

Capture and restore stream state in formatting helpers. Because format flags, fill character, and precision are mutable stream state, any utility function that sets them without restoring them will corrupt the caller's formatting:

cpp
#include <iomanip>
#include <iostream>

void print_hex32(std::ostream& out, unsigned v) {
    const auto  flags = out.flags();
    const char  fill  = out.fill();
    const auto  prec  = out.precision();

    out << "0x" << std::hex << std::uppercase
        << std::setfill('0') << std::setw(8) << v;

    out.flags(flags);
    out.fill(fill);
    out.precision(prec);
}

Prefer std::format (C++20) for new code. <iomanip> manipulators mutate hidden stream state and compose poorly; interleaved calls from different call sites corrupt each other silently. std::format from <format> is stateless, positional, type-safe, and roughly 2–3Γ— faster to format:

cpp
#include <format>   // C++20
#include <iostream>

// No state mutation, intent is local and explicit
std::cout << std::format("{:<14}{:>10.2f}\n", name, price);

Use setiosflags/resetiosflags to set multiple flags atomically when you need to combine flags that std::hex etc. would set one at a time.

Common Pitfalls

setw is not sticky β€” the most common <iomanip> mistake. Applying setw once and then outputting multiple values gives the first value the desired width and all remaining values the default width of zero:

cpp
std::cout << std::setw(8);   // applies only to the very next insertion
std::cout << a << b;         // b gets width 0

put_money and get_money silently produce wrong results in the "C" locale. The "C" locale defines no currency symbol or digit grouping. Imbue an explicit named locale before use, and handle the possibility that the locale name is not available on the target platform.

get_time does not zero-initialize std::tm. Fields not mentioned in the format string are left at whatever value the struct contained before the call. Always value-initialize with std::tm t{}; before passing it to get_time.

setbase silently falls back to base 10 for unsupported values. Arguments other than 8, 10, or 16 cause all basefield bits to be cleared, which the stream interprets as decimal. There is no error or diagnostic.

Stale precision after std::fixed. std::fixed changes what setprecision means: without it, precision counts significant figures; with it, precision counts decimal places. Switching between fixed and defaultfloat without re-issuing setprecision produces confusing output because the stored precision value carries over.

RAII Format State Guard

Stream formatting flags are shared mutable state β€” a function that sets them without restoring them corrupts every subsequent output call on that stream. The standard library's std::ios_base::flags(), std::ios_base::precision(), and std::basic_ios::fill() getters and setters make it straightforward to save and restore the complete formatting state with a small RAII guard.

The guard below captures all three pieces of state on construction and restores them unconditionally in its destructor, making it safe even when the formatting code throws:

cpp
#include <iomanip>
#include <iostream>
#include <ios>

class FormatGuard {
public:
    explicit FormatGuard(std::ios_base& stream)
        : stream_(stream),
          flags_(stream.flags()),
          precision_(stream.precision()),
          fill_(static_cast<std::basic_ios<char>&>(stream).fill())
    {}

    ~FormatGuard() {
        stream_.flags(flags_);
        stream_.precision(precision_);
        static_cast<std::basic_ios<char>&>(stream_).fill(fill_);
    }

    FormatGuard(const FormatGuard&) = delete;
    FormatGuard& operator=(const FormatGuard&) = delete;

private:
    std::ios_base&      stream_;
    std::ios_base::fmtflags flags_;
    std::streamsize         precision_;
    char                    fill_;
};

The problem it solves: format state leaks silently across call boundaries. Without a guard, a hex dump helper leaves the stream in hex mode for all callers that follow:

cpp
// Without FormatGuard β€” state leak
void bad_hex_dump(std::ostream& out, unsigned v) {
    out << std::hex << std::setfill('0') << std::setw(8) << v << '\n';
    // hex and setfill remain set after this returns!
}

// With FormatGuard β€” caller's state is always restored
void hex_dump(std::ostream& out, unsigned v) {
    FormatGuard guard(out);                          // saves state
    out << "0x" << std::hex << std::uppercase
        << std::setfill('0') << std::setw(8) << v << '\n';
}                                                    // restores state

int main() {
    hex_dump(std::cout, 0xDEADBEEF);
    // Still in decimal here β€” FormatGuard ensured restoration
    std::cout << 255 << '\n';   // prints 255, not ff
}

Printing Tables and Aligned Output

std::setw controls the minimum field width of the very next insertion, while std::left and std::right choose which side to pad. Together with std::setfill for custom padding characters and std::setprecision for numeric columns, they are sufficient to produce readable fixed-width tables β€” provided you remember to reissue setw before every column (it is not sticky).

The following example prints a vector of result records as a formatted table with a separator line:

cpp
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

struct Result {
    std::string name;
    double      score;
    int         rank;
};

void print_table(const std::vector<Result>& results) {
    // Column widths
    constexpr int W_NAME  = 20;
    constexpr int W_SCORE = 10;
    constexpr int W_RANK  =  6;
    constexpr int W_TOTAL = W_NAME + W_SCORE + W_RANK;

    std::cout << std::fixed << std::setprecision(2);

    // Header
    std::cout << std::left  << std::setw(W_NAME)  << "Name"
              << std::right << std::setw(W_SCORE) << "Score"
                            << std::setw(W_RANK)  << "Rank"
              << '\n';

    // Separator
    std::cout << std::setfill('-') << std::setw(W_TOTAL) << "" << '\n';
    std::cout << std::setfill(' ');   // reset fill β€” setfill is sticky

    // Rows
    for (const auto& r : results) {   // C++11
        std::cout << std::left  << std::setw(W_NAME)  << r.name
                  << std::right << std::setw(W_SCORE) << r.score
                                << std::setw(W_RANK)  << r.rank
                  << '\n';
    }
}

int main() {
    print_table({
        {"Alice",  98.75, 1},
        {"Bob",    87.50, 2},
        {"Charlie",76.00, 3},
    });
}
// Name                     Score  Rank
// ------------------------------------
// Alice                    98.75     1
// Bob                      87.50     2
// Charlie                  76.00     3

setw only applies to the next output operation. Every column in every row needs its own setw call; a common mistake is to set it once at the top of a loop and expect it to apply across multiple << operators in the same statement.

Floating-Point Formatting: fixed, scientific, hexfloat

std::fixed, std::scientific, and std::hexfloat select the representation; std::setprecision controls the digit count within that representation. These manipulators are sticky β€” switch representations explicitly when you need to change back, and consider whether the stored precision value still makes sense after switching.

cpp
#include <iomanip>
#include <iostream>
#include <limits>

int main() {
    const double pi = 3.141592653589793;

    // Financial output: fixed decimal places
    std::cout << std::fixed << std::setprecision(2) << pi << '\n';
    // 3.14

    // Engineering notation: exponent form
    std::cout << std::scientific << std::setprecision(4) << pi << '\n';
    // 3.1416e+00

    // Exact binary representation β€” no rounding error           // C++11
    std::cout << std::hexfloat << pi << '\n';
    // 0x1.921fb54442d18p+1

    // Reset to default (significant digits, shortest representation)
    std::cout << std::defaultfloat << pi << '\n';
    // 3.14159

    // Round-trip precision: use digits10 significant digits
    std::cout << std::setprecision(std::numeric_limits<double>::digits10)
              << std::defaultfloat << pi << '\n';
    // 3.14159265358979  (15 digits for double)
}

std::hexfloat (C++11) is the only notation that guarantees an exact binary round-trip for IEEE 754 doubles. For serialisation of floating-point values where exact reproduction matters β€” configuration files, test fixtures, neural-network weights β€” prefer hexfloat or std::format("{:a}", v) (C++20) over fixed/scientific.

Integer Base Formatting: hex, oct, dec, showbase

std::hex, std::oct, and std::dec are sticky flags from <ios> that change how every subsequent integer is printed, so always reset to std::dec when you are done. std::showbase adds the conventional prefix (0x for hex, 0 for octal) so that the base is self-describing in the output.

cpp
#include <iomanip>
#include <iostream>

int main() {
    const unsigned value = 255;

    std::cout << std::hex << std::showbase;
    std::cout << value << '\n';    // 0xff

    std::cout << std::uppercase;
    std::cout << value << '\n';    // 0XFF

    std::cout << std::oct;
    std::cout << value << '\n';    // 0377

    std::cout << std::noshowbase << std::nouppercase;

    // Zero-padded hex: setfill + setw every time (setw is not sticky)
    std::cout << std::hex
              << std::setfill('0') << std::setw(8) << value << '\n';
    // 000000ff

    // Reset all integer base formatting to decimal
    std::cout << std::dec << std::setfill(' ');
    std::cout << value << '\n';   // 255
}

Because these flags are sticky they persist across function calls, across loop iterations, and even across translation units that share the same stream object. A function that switches to std::hex for debug output and returns without calling std::dec will cause every integer output in the rest of the program to be printed in hex β€” often far from where the flag was set, making the bug difficult to locate.

Locale-Aware Formatting

std::imbue installs a locale on a stream, and all subsequent locale-sensitive operations β€” number grouping, decimal point character, monetary symbol β€” then use that locale's facets. std::locale("") constructs the locale from the process environment (the LANG/LC_ALL family of variables), giving the user's native formatting.

cpp
#include <iomanip>
#include <iostream>
#include <locale>
#include <sstream>

int main() {
    // Print a large number with grouping separators (e.g. 1,234,567 in en_US)
    std::cout.imbue(std::locale("en_US.UTF-8"));
    std::cout << std::fixed << std::setprecision(2);
    std::cout << 1234567.89 << '\n';   // 1,234,567.89

    // German locale: decimal comma, dot thousands separator
    std::cout.imbue(std::locale("de_DE.UTF-8"));
    std::cout << 1234567.89 << '\n';   // 1.234.567,89

    // Inspect the facet directly
    std::locale loc("en_US.UTF-8");
    const auto& np = std::use_facet<std::numpunct<char>>(loc);
    std::cout.imbue(std::locale::classic());   // restore "C" locale
    std::cout << "decimal point : " << np.decimal_point() << '\n'; // .
    std::cout << "thousands sep : " << np.thousands_sep()  << '\n'; // ,
    std::cout << "grouping      : ";
    for (unsigned char g : np.grouping())
        std::cout << static_cast<int>(g) << ' '; // 3 β€” groups of 3
    std::cout << '\n';
}

C++20 std::format is locale-independent by default, which avoids surprises when code runs in environments with unexpected locale settings. To opt into locale-sensitive formatting, pass std::locale to std::vformat or use the L conversion specifier (e.g. "{:L}") with a locale-aware format call. For most new code, the explicit opt-in model of std::format is preferable to <iomanip>'s implicit ambient-locale model.

iomanip vs std::format (C++20)

std::format (C++20) should be the default choice for new formatting code; <iomanip> remains the right tool for stream-based APIs, existing operator<< overloads, and pre-C++20 codebases. The two approaches differ across several axes:

Criterion<iomanip>std::format (C++20)
Compile-time format safetyNo β€” runtime failures or silent wrong outputYes β€” ill-formed format strings are compiler errors (with std::format_string)
PerformanceModerate β€” virtual dispatch through std::locale facetsFast β€” typically 2–4Γ— faster than printf-family in benchmarks
Positional argumentsNoYes β€” {0}, {1} for reordering
Locale handlingImplicit ambient locale; easy to corrupt globallyLocale-independent by default; opt-in with L specifier
Streaming APINative β€” works with any std::ostreamNot streaming; produces std::string (use std::print for stdout)
Custom typesoperator<< overloadstd::formatter<T> specialisation
operator<< integrationDirectRequires adapter (e.g. out << std::format(...))

Converting an <iomanip>-based logger to std::format removes all hidden state and makes format strings auditable at a glance:

cpp
// Before β€” iomanip (C++98/11)
void log_event_old(std::ostream& out, const std::string& tag, double elapsed) {
    const auto flags = out.flags();
    out << "[" << std::left << std::setw(12) << tag << "] "
        << std::fixed << std::setprecision(3) << elapsed << "s\n";
    out.flags(flags);
}

// After β€” std::format (C++20)
void log_event(std::ostream& out, std::string_view tag, double elapsed) {
    out << std::format("[{:<12}] {:.3f}s\n", tag, elapsed); // C++20
}

The std::format version has no mutable state, no save/restore boilerplate, and the complete format spec is visible inline. Use std::print instead of std::cout << when writing directly to stdout or stderr β€” it avoids constructing an intermediate std::string.

See Also

  • <ios> β€” non-parameterized format manipulators: std::fixed, std::hex, std::left, std::boolalpha, etc.
  • <format> (C++20) β€” std::format and std::print: stateless, type-safe replacement for most <iomanip> use cases
  • <locale> β€” std::money_put, std::money_get, std::time_put, std::time_get facets underlying the C++11 manipulators
  • <chrono> β€” C++20 adds direct operator<< for std::chrono duration and time-point types, reducing the need for put_time