Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Modern C++ Programming Cookbook

You're reading from   Modern C++ Programming Cookbook Master C++ core language and standard library features, with over 100 recipes, updated to C++20

Arrow left icon
Product type Paperback
Published in Sep 2020
Publisher Packt
ISBN-13 9781800208988
Length 750 pages
Edition 2nd Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Marius Bancila Marius Bancila
Author Profile Icon Marius Bancila
Marius Bancila
Arrow right icon
View More author details
Toc

Table of Contents (16) Chapters Close

Preface Learning Modern Core Language Features Working with Numbers and Strings FREE CHAPTER Exploring Functions Preprocessing and Compilation Standard Library Containers, Algorithms, and Iterators General-Purpose Utilities Working with Files and Streams Leveraging Threading and Concurrency Robustness and Performance Implementing Patterns and Idioms Exploring Testing Frameworks C Plus Plus 20 Core Features Bibliography Other Books You May Enjoy
Index

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 or std::vector<char>, using std::format_n() and using the std::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 to std::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, or 0x 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 or G 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, s

Copies the string to the output.

Integral types

B

Binary format with 0b as a prefix.

B

Binary format with 0B as a prefix.

C

Character format. Copies the value to the output as it was a character type.

none or d

Decimal format.

O

Octal format with 0 as a prefix (unless the value is 0).

X

Hexadecimal format with 0x as a prefix.

X

Hexadecimal format with 0X as a prefix.

char and wchar_t

none or c

Copies the character to the output.

b, B, c, d, o, x, X

Integer presentation types.

bool

none or s

Copies true or false as a textual representation (or their local-specific form) to the output.

b, B, c, d, o, x, X

Integer presentation types.

Floating-point

A

Hexadecimal representation. Same as if calling std::to_chars(first, last, value, std::chars_format::hex, precision) or std::to_chars(first, last, value, std::chars_format::hex), depending on whether precision is specified or not.

A

Same as a except that it uses uppercase letters for digits above 9 and uses P to indicate the exponent.

E

Scientific representation. Produces the output as if calling std::to_chars(first, last, value, std::chars_format::scientific, precision).

E

Similar to e except that it uses E to indicate the exponent.

f, F

Fixed representation. Produces the output as if by calling std::to_chars(first, last, value, std::chars_format::fixed, precision). When no precision is specified, the default is 6.

G

General floating-point representation. Produces the output as if by calling std::to_chars(first, last, value, std::chars_format::general, precision). When no precision is specified, the default is 6.

G

Same as g except that it uses E to indicate the exponent.

Pointer

none or p

Pointer representation. Produces the output as if by calling std::to_chars(first, last, reinterpret_cast<std::uintptr_t>(value), 16) with the prefix 0x added to the output. This is available only when std::uintptr_t is defined; otherwise, the output is implementation-defined.

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 % character.

%n

Writes a newline character.

%t

Writes a horizontal tab character.

%Y

Writes the year as a decimal number. If the result is less than four digits, it is left-padded with 0 to four digits.

%m

Writes the month as a decimal number (January is 01). If the result is a single digit, it is prefixed with 0.

%d

Writes the day of month as a decimal number. If the result is a single decimal digit, it is prefixed with 0.

%w

Writes the weekday as a decimal number (0-6), where Sunday is 0.

%D

Equivalent to %m/%d/%y.

%F

Equivalent to %Y-%m-%d.

%H

Writes the hour (24-hour clock) as a decimal number. If the result is a single digit, it is prefixed with 0.

%I

Writes the hour (12-hour clock) as a decimal number. If the result is a single digit, it is prefixed with 0.

%M

Writes the minute as a decimal number. If the result is a single digit, it is prefixed with 0.

%S

Writes the second as a decimal number. If the number of seconds is less than 10, the result is prefixed with 0.

%R

Equivalent to %H:%M.

%T

Equivalent to %H:%M:%S.

%X

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
You have been reading a chapter from
Modern C++ Programming Cookbook - Second Edition
Published in: Sep 2020
Publisher: Packt
ISBN-13: 9781800208988
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime