The Formatting Library in C++20: The Format String

I introduced “The Formatting Library in C++20” in my last post. Today, I will dive deeper into the format specification of the format string.

Let me start with a short recap of the format string.

  • Syntax: std::format(FormatString, Args)

The format string FormatString consists of

  • Ordinary characters (except { and })
  • Escape sequences {{ and }} that are replaced by { and }
  • Replacement fields

A replacement field has the format { }.

Argument ID

You can use an argument id and a colon inside the replacement field followed by a format specification. Both components are optional. The argument id allows you to specify the index of the arguments in Args. The ids start with 0. When you don’t provide the argument id, the fields are filled in the same order as the arguments are given. Either all replacement fields have to use an argument id or none.

Thanks to the argument id, you can reorder or address particular arguments.

// formatArgumentID.cpp

#include <format>
#include <iostream>
#include <string>
 
int main() {
    
    std::cout << '\n';

    std::cout << std::format("{} {}: {}!\n", "Hello", "World", 2020);        // (1)

    std::cout << std::format("{1} {0}: {2}!\n", "World", "Hello", 2020);     // (2)

    std::cout << std::format("{0} {0} {1}: {2}!\n", "Hello", "World", 2020); // (3)

    std::cout << std::format("{0}: {2}!\n", "Hello", "World", 2020);         // (4)
    
    std::cout << '\n';
   
}

Line (1) displays the argument in the given order. On the contrary, line (2) reorders the first and second argument, line (3) shows the first argument twice, and line (4) ignores the second argument.

For completeness, here is the output of the program:

Applying the argument id with the format specification makes formatting text in C++20 very powerful.

Format Specification

I won’t present the format specification for basic types, string types, or chrono types. For basic types and std::string, read the full details here: standard format specification. Accordingly, you can find the details of chrono types here: chrono format specification.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    Instead, I present a pragmatic description of the format string for basic types, string types, and chrono types.

    fill_align(opt) sign(opt) #(opt) 0(opt) width(opt) precision(opt) L(opt) type(opt) 
    

    All parts are optional (opt). The following few sections present the features of this format specification.

    Fill Character and Alignment

    The fill character is optional (any character except { or }) and followed by an alignment specification. To use the fill character, you must specify the alignment.

    • Fill character: by default, space is used
    • Alignment:
      • <: left (default for non-numbers)
      • >: right (default for numbers)
      • ^: center
    // formatFillAlign.cpp
    
    #include <format>
    #include <iostream>
     
    int main() {
    
        std::cout << '\n';
        
        int num = 2020;
    
        std::cout << std::format("{:6}", num) << '\n'; 
        std::cout << std::format("{:6}", 'x') << '\n';   
        std::cout << std::format("{:*<6}", 'x') << '\n';
        std::cout << std::format("{:*>6}", 'x') << '\n';
        std::cout << std::format("{:*^6}", 'x') << '\n';
        std::cout << std::format("{:6d}", num) << '\n';
        std::cout << std::format("{:6}", true) << '\n';
    
        std::cout << '\n';
       
    }
    

    The default alignment depends on the used types. In contrast to the iostream operator, boolean values are displayed by default as true or false.

    Sign, # and 0

    The sign, #, and 0 character is only valid when an integer or floating-point type is used.

    The sign can have the following values:

    • +: sign is used for zero and positive numbers
    • -: sign is only used for negative numbers (default)
    • space: leading space is used for non-negative numbers and a minus sign for negative numbers
    // formatSign.cpp
    
    #include <format>
    #include <iostream>
     
    int main() {
    
        std::cout << '\n';
    
        std::cout << std::format("{0:},{0:+},{0:-},{0: }", 0) << '\n';
        std::cout << std::format("{0:},{0:+},{0:-},{0: }", -0) << '\n';
        std::cout << std::format("{0:},{0:+},{0:-},{0: }", 1) << '\n';
        std::cout << std::format("{0:},{0:+},{0:-},{0: }", -1) << '\n';
    
        std::cout << '\n';
       
    }
    

    The # causes the alternative form:

    • For integer types, the prefix 0b, 0, or 0x is used for binary, octal, or hexadecimal presented types
    • For floating-point types, a decimal point is always used
    • 0: pads with leading zeros
    // formatAlternate.cpp
    
    #include <format>
    #include <iostream>
     
    int main() {
    
        std::cout << '\n';
    
        std::cout << std::format("{:#015}", 0x78) << '\n';
        std::cout << std::format("{:#015b}", 0x78) << '\n';
        std::cout << std::format("{:#015x}", 0x78) << '\n';
    
        std::cout << '\n';
    
        std::cout << std::format("{:g}", 120.0) << '\n';
        std::cout << std::format("{:#g}", 120.0) << '\n';
    
        std::cout << '\n';
       
    }
    

    What’s Next?

    The format specifier allows you to specify the width, precision, and the type of the value. I will write about it in my next post.

    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, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, 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, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.

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

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,