Formatting text with std::format
The C++ language has two ways of formatting text: the printf
family of functions and the I/O streams library. The printf
functions are inherited from C and provide a separation of the formatting text and the arguments. The streams library provides safety and extensibility and is usually recommended over printf
functions, but is, in general, slower. The C++20 standard proposes a new formatting library alternative for output formatting, which is similar in form to printf
but safe and extensible and is intended to complement the existing streams library. In this recipe, we will learn how to use the new functionalities instead of the printf
functions or the streams library.
Getting ready
The new formatting library is available in the header <format>
. You must include this header for the following samples to work.
How to do it...
The std::format()
function formats its arguments according to the provided formatting string. You can use it as follows:
- Provide empty replacement fields, represented by
{}
, in the format string for each argument:auto text = std::format("{} is {}", "John", 42);
- Specify the 0-based index of each argument in the argument list inside the replacement field, such as
{0}
,{1}
, and so on. The order of the arguments is not important, but the index must be valid:auto text = std::format("{0} is {1}", "John", 42);
- Control the output text with format specifiers provided in the replacement field after a colon (
:
). For basic and string types, this is a standard format specification. For chrono types, this is a chrono format specification:auto text = std::format("{0} hex is {0:08X}", 42); auto now = std::chrono::system_clock::now(); auto time = std::chrono::system_clock::to_time_t(now); auto text = std::format("Today is {:%Y-%m-%d}", *std::localtime(&time));
You can also write the arguments in an out format using an iterator with either std::format_to()
or std::format_to_n()
, as follows:
- Write to a buffer, such as an
std::string
orstd::vector<char>
, usingstd::format_n()
and using thestd::back_inserter()
helper function:std::vector<char> buf; std::format_to(std::back_inserter(buf), "{} is {}", "John", 42);
- Use
std::formatted_size()
to retrieve the number of characters necessary to store the formatted representation of the arguments:auto size = std::formatted_size("{} is {}", "John", 42); std::vector<char> buf(size); std::format_to(buf.data(), "{} is {}", "John", 42);
- To limit the number of characters written to the output buffer, you can use
std::format_to_n()
, which is similar tostd::format_to()
but writes, at most,n
characters:char buf[100]; auto result = std::format_to_n(buf, sizeof(buf), "{} is {}", "John", 42);
How it works...
The std::format()
function has multiple overloads. You can specify the format string either as a string view or a wide string view, with the function returning either an std::string
or an std::wstring
. You can also specify, as the first argument, an std::locale
, which is used for locale-specific formatting. The function overloads are all variadic function templates, which means you can specify any number of arguments after the format.
The format string consists of ordinary characters, replacement fields, and escape sequences. The escape sequences are {{
and }}
and are replaced with {
and }
in the output. A replacement field is provided within curly brackets {}
. It can optionally contain a non-negative number, representing the 0-based index of the argument to be formatted, and a colon (:
), followed by a format specifier. If the format specifier is invalid, an exception of the type std::format_error
is thrown.
In a similar manner, std::format_to()
has multiple overloads, just like std::format()
. The difference between these two is that std::format_to()
always takes an iterator to the output buffer as the first argument and returns an iterator past the end of the output range (and not a string as std::format()
does). On the other hand, std::format_to_n()
has one more parameter than std::format_to()
. Its second parameter is a number representing the maximum number of characters to be written to the buffer.
The following listing shows the signature of the simplest overload of each of these three function templates:
template<class... Args>
std::string format(std::string_view fmt, const Args&... args);
template<class OutputIt, class... Args>
OutputIt format_to(OutputIt out,
std::string_view fmt, const Args&... args);
template<class OutputIt, class... Args>
std::format_to_n_result<OutputIt>
format_to_n(OutputIt out, std::iter_difference_t<OutputIt> n,
std::string_view fmt, const Args&... args);
When you provide the format string, you can supply argument identifiers (their 0-based index) or omit them. However, it is illegal to use both. If the indexes are omitted in the replacement fields, the arguments are processed in the provided order, and the number of replacement fields must not be greater than the number of supplied arguments. If indexes are provided, they must be valid for the format string to be valid.
When a format specification is used, then:
- For basic types and string types, it is considered to be a standard format specification.
- For chrono types, it is considered to be a chrono format specification.
- For user-defined types, it is defined by a user-defined specialization of the
std::formatter
class for the desired type.
The standard format specification is based on the format specification in Python and has the following syntax:
fill-and-align(optional) sign(optional) #(optional) 0(optional) width(optional) precision(optional) L(optional) type(optional)
These syntax parts are briefly described here.
fill-and-align
is an optional fill character, followed by one of the align options:
<
: Forces the field to be left-aligned with the available space.>
: Forces the field to be right-aligned with the available space.^
: Forces the field to be centered with the available space. To do so, it will insert n/2 characters to the left and n/2 characters to the right:auto t1 = std::format("{:5}", 42); // " 42" auto t2 = std::format("{:5}", 'x'); // "x " auto t3 = std::format("{:*<5}", 'x'); // "x****" auto t4 = std::format("{:*>5}", 'x'); // "****x" auto t5 = std::format("{:*^5}", 'x'); // "**x**" auto t6 = std::format("{:5}", true); // "true "
sign
, #
, and 0
are only valid when a number (either an integer or a floating-point) is used. The sign can be one of:
+
: Specifies that the sign must be used for both negative and positive numbers.-
: Specifies that the sign must be used only for negative numbers (which is the implicit behavior).- A space: Specifies that the sign must be used for negative numbers and that a leading space must be used for non-negative numbers:
auto t7 = std::format("{0:},{0:+},{0:-},{0: }", 42); // "42,+42,42, 42" auto t8 = std::format("{0:},{0:+},{0:-},{0: }", -42); // "-42,-42,-42,-42"
The symbol #
causes the alternate form to be used. This can be one of the following:
- For integral types, when binary, octal, or hexadecimal representation is specified, the alternate form adds the prefix
0b
,0
, or0x
to the output. - For floating-point types, the alternate form causes a decimal-point character to always be present in the formatted value, even if no digits follow it. In addition, when
g
orG
are used, the trailing zeros are not removed from the output.
The digit 0
specifies that leading zeros should be outputted to the field width, except when the value of a floating-point type is infinity or NaN
. When present alongside an align option, the specifier 0
is ignored:
auto t9 = std::format("{:+05d}", 42); // "+0042"
auto t10 = std::format("{:#05x}", 42); // "0x02a"
auto t11 = std::format("{:<05}", -42); // "-42 "
width
specifies the minimum field width and can be either a positive decimal number or a nested replacement field. The precision
field indicates the precision for floating-point types or, for string types, how many characters will be used from the string. It is specified with a dot (.
), followed by a non-negative decimal number or a nested replacement field.
Locale-specific formatting is specified with the uppercase L
and causes the locale-specific form to be used. This option is only available for arithmetic types.
The optional type
determines how the data will be presented in the output. The available string presentation types are shown in the following table:
Type |
Presentation type |
Description |
Strings |
none, |
Copies the string to the output. |
Integral types |
|
Binary format with 0b as a prefix. |
|
Binary format with 0B as a prefix. |
|
|
Character format. Copies the value to the output as it was a character type. |
|
none or |
Decimal format. |
|
|
Octal format with 0 as a prefix (unless the value is 0). |
|
|
Hexadecimal format with 0x as a prefix. |
|
|
Hexadecimal format with 0X as a prefix. |
|
|
none or |
Copies the character to the output. |
|
Integer presentation types. |
|
|
none or |
Copies true or false as a textual representation (or their local-specific form) to the output. |
|
Integer presentation types. |
|
Floating-point |
|
Hexadecimal representation. Same as if calling |
|
Same as |
|
|
Scientific representation. Produces the output as if calling |
|
|
Similar to |
|
|
Fixed representation. Produces the output as if by calling |
|
|
General floating-point representation. Produces the output as if by calling |
|
|
Same as |
|
Pointer |
none or |
Pointer representation. Produces the output as if by calling |
The chrono format specification has the following form:
fill-and-align(optional) width(optional) precision(optional) chrono-spec(optional)
The fill-and-align
, width
, and precision
fields have the same meaning as in the standard format specification, described previously. The precision is only valid for std::chrono::duration
types when the representation type is a floating-point type. Using it in other cases throws an std::format_error
exception.
The chrono specification can be empty, in which case the argument is formatted as if by streaming it to an std::stringstream
and copying the result string. Alternatively, it can consist of a series of conversion specifiers and ordinary characters. Some of these format specifiers are presented in the following table:
Conversion specifier |
Description |
|
Writes a literal |
|
Writes a newline character. |
|
Writes a horizontal tab character. |
|
Writes the year as a decimal number. If the result is less than four digits, it is left-padded with |
|
Writes the month as a decimal number (January is |
|
Writes the day of month as a decimal number. If the result is a single decimal digit, it is prefixed with |
|
Writes the weekday as a decimal number ( |
|
Equivalent to |
|
Equivalent to |
|
Writes the hour (24-hour clock) as a decimal number. If the result is a single digit, it is prefixed with |
|
Writes the hour (12-hour clock) as a decimal number. If the result is a single digit, it is prefixed with |
|
Writes the minute as a decimal number. If the result is a single digit, it is prefixed with |
|
Writes the second as a decimal number. If the number of seconds is less than 10, the result is prefixed with |
|
Equivalent to |
|
Equivalent to |
|
Writes the locale's time representation. |
The complete list of format specifiers for the chrono library can be consulted at https://en.cppreference.com/w/cpp/chrono/system_clock/formatter.
See also
- Using std::format with user-defined types to learn how to create custom formatting specialization for user-defined types
- Converting between numeric and string types to learn how to convert between numbers and strings