std::format in C++20

Contents[Show]

Today, I'm happy to present Peter Gottschling's guest post to the new formatting library in C++20: std::format.  Thanks to std::format, text formatting becomes in C++20 as easy as in Python.

 TimelineCpp20CoreLanguage

Peter is the author of the must-read book "Discovering Modern C++"  for professional C++ developers.

New Formatting

Traditional stream formatting requires a fair amount of typing. Format strings in printf and alike are more expressive and allow us to declare with few symbols what we've written with multiple I/O manipulators. 

Nonetheless, we advise against using printf. For two reasons, it can't be used with user types and is not type-safe. The format string is parsed at run time, and the following arguments are treated with an obscure macro mechanism. If the arguments don't match the format string, the behavior is undefined and can cause program crashes. For instance, a string is passed as a pointer, and from the pointed address on, the bytes are read and printed as char until a binary 0 is found in memory. If we accidentally try printing an int as a string, the int value is misinterpreted as an address from which a sequence  char shall be printed. This will result in either absolute nonsensical output or (more likely) a memory error if the address is inaccessible. We must admit that recent compilers parse format strings (when known at compile time) and warn about argument mismatches.

The new format library {By the time of writing, no compiler supported the library, and the examples were implemented with its prototype version: the fmt library. The format library combines the expressibility of the format string with the type safety and the user-extensibility of stream I/O. It adds the opportunity to reorder the arguments in the output.

Integrals

Instead of a formal specification, we port some printf examples from cppreference.com to the new format:

print("Decimal:\t{} {} {:06} {} {:0} {:+} {:d}\n", 1, 2, 3, 0, 0, 4, -1);

print("Hexadecimal:\t{:x} {:x} {:X} {:#x}\n", 5, 10, 10, 6);

print("Octal:\t\t{:o} {:#o} {:#o}\n", 10, 10, 4);

print("Binary:\t\t{:b} {:#b} {:#b}\n", 10, 10, 4);

 

This snippet prints:

Decimal:        1 2 000003 0 0 +4 -1

Hexadecimal:    5 a A 0x6

Octal:          12 012 04

Binary:         1010 0b1010 0b100

The first two numbers were just printed without giving any format information. The exact output is generated when we ask for a decimal number with the format specifier :d. The third number shall be printed (minimally) 6~characters wide and filled with 0s. The specifier + allows us to force printing the sign for all numbers. printf allows for specifying unsigned the output of numbers. That leads to incorrect large numbers when the value to print is negative. The format library refrains from user declarations of  unsigned output since this information is already contained in the type of the according to the argument. If somebody wants to print a negative value as a largely positive, he must convert it explicitly.

The second line demonstrates that we can print values hexadecimally with the lower and upper case for digits larger than 9. The specifier # generates the prefix 0x used in hexadecimal literals.

Likewise, we can print the values as octal and binaries, optionally with the according to literal prefix.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Be part of my mentoring programs:

 

 

 

 

Do you want to stay informed about my mentoring programs: Subscribe via E-Mail.

Floating-Point Numbers

With floating-point numbers, we have more formatting options:

print("Default:\t{} {:g} {:g}\n", 1.5, 1.5, 1e20);

print("Rounding:\t{:f} {:.0f} {:.22f}\n", 1.5, 1.5, 1.3);

print("Padding:\t{:05.2f} {:.2f} {:5.2f}\n", 1.5, 1.5, 1.5);

print("Scientific:\t{:E} {:e}\n", 1.5, 1.5);

print("Hexadecimal:\t{:a} {:A}\n\n", 1.5, 1.3);

 

Then we get:

Default:        1.5 1.5 1e+20

Rounding:       1.500000 2 1.3000000000000000444089

Padding:        01.50 1.50  1.50

Scientific:     1.500000E+00 1.500000e+00

Hexadecimal:    0x1.8p+0 0X1.4CCCCCCCCCCCDP+0

 

We get the default output with empty braces or only containing a colon. This corresponds to the format specifier :g and yields the same output as streams without the manipulators. The number of fractional digits can be given between a dot and the format specifier f. Then the value is rounded to that precision. If the requested number is larger than what is representable by the value's type, the last digits aren't significant. A digit in front of the dot specifies the (minimal) width of the output. As with integers, we can request leading 0s. Floating-point numbers can be printed in scientific notation with either upper or lower case e to start the exponential part. The hexadecimal output can initialize a variable in another program with precisely the same bits.

Redirecting Output

The output can be redirected to any other std::ostream (Requires including ostream.h with the fmt library.):

print(std::cerr, "System error code = {}\n", 7);

ofstream error_file("error_file.txt");

print(error_file, "System error code = {}\n", 7);

Reordering Arguments and Name them

In contrast to printf, arguments can now be reordered:

print("I'd rather be {1} than {0}.\n", "right", "happy");

 

In addition to referring to the arguments by their positions, we can give them names:

print("Hello, {name}! The answer is {number}. Goodbye, {name}.\n",
      arg("name", name), arg("number", number));

 

Or, more concisely:

print("Hello, {name}! The answer is {number}. Goodbye, {name}.\n",
      "name"_a=name, "number"_a=number);

 

  • Rainer: C++20 does not have named arguments.

The example also demonstrates that we can print an argument multiple times.

Reordering arguments is critical in multi-lingual software to provide a natural phrasing.

Now, we want to print the average of two values in five languages:

void print_average(float v1, float v2, int language)

{   

    using namespace fmt;

    string formats[]= {"The average of {v1} and {v2} is {result}.\n",

                       "{result:.6f} ist der Durchschnitt von {v1} und {v2}.\n",

                       "La moyenne de {v1} et {v2} est {result}.\n",

                       "El promedio de {v1} y {v2} es {result}.\n",

                       "{result} corrisponde alla media di {v1} e {v2}.\n"};

    print (formats[language], "v1"_a= v1, "v2"_a= v2, "result"_a= (v1+v2)/2.0f);

}   

 

Of course, the German version is the most pedantic one, requesting six decimal digits no matter what:

The average of 3.5 and 7.3 is 5.4.

5.400000 ist der Durchschnitt von 3.5 und 7.3.

La moyenne de 3.5 et 7.3 est 5.4.

El promedio de 3.5 y 7.3 es 5.4.

5.4 corrisponde alla media di 3.5 e 7.3.

Admittedly, this example would have worked without reordering the arguments, but it nicely demonstrates the important possibility of separating the text and the formatting from the values. To store formatted text in a string, we don't need  stringstream any longer but can do it directly with the function format.

What's next?

In the next post, Peter continues his introduction to std::format. He writes about user-defined formatting.

 

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, and Rob North.

 

Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, and Slavko Radman.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

 

My special thanks to Take Up Code TakeUpCode 450 60

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

  • C++ - The Core Language
  • C++ - The Standard Library
  • C++ - Compact
  • C++11 and C++14
  • Concurrency with Modern C++
  • Design Pattern and Architectural Pattern with C++
  • Embedded Programming with Modern C++
  • Generic Programming (Templates) with C++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

Tags: format

Comments   

+1 #1 Spencer Collyer 2020-11-29 18:08
I don't think the C++20 standard includes named arguments for std::format. I know {fmt} does, but that wasn't part of the P0645 proposal was it?
Quote
0 #2 Rainer Grimm 2020-12-01 21:35
Quoting Spencer Collyer:
I don't think the C++20 standard includes named arguments for std::format. I know {fmt} does, but that wasn't part of the P0645 proposal was it?

You are right. Peter made an error. I make a remark to the post.
Quote
+2 #3 Spencer Collyer 2021-04-24 09:23
Also, C++20 doesn't support the 'print' function of {fmt}. Hopefully it will appear in C++23 (there is a proposal out for it).

You need to use 'cout
Quote
0 #4 jj 2022-09-14 18:15
As of September 2022, there still appears to be no compilers in existence that ship with the header in C++20 (or 23) mode, so this whole thing is all pretty much vapor, anyways.

That includes GCC 12.2, clang 15.0.0, MSVC 19.32, icc 2021.6, and icx 2022.1.0, among others.
Quote

Stay Informed about my Mentoring

 

Mentoring

English Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Course: The All-in-One Guide to C++20

Course: Master Software Design Patterns and Architecture in C++

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 4013

Yesterday 4344

Week 40891

Month 21137

All 12099346

Currently are 176 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments