More Details to Formatting User-Defined Types in C++20

Implementing a formatter for a user-defined type having more than one value in C++20 is challenging.

This post is the 5th post in my miniseries about formatting in C++20. Read the previous ones here:

A Formatter for More Values

Point is a class with three members.

// formatPoint.cpp

#include <format>
#include <iostream>
#include <string>

struct Point {
    int x{2017};
    int y{2020};
    int z{2023};
};

template <>
struct std::formatter<Point> : std::formatter<std::string> {
    auto format(Point point, format_context& context) const {
        return formatter<string>::format(
               std::format("({}, {}, {})", point.x, point.y, point.y), context);
  }
};

int main() {

    std::cout << '\n';

    Point point;

    std::cout << std::format("{:*<25}", point) << '\n';        // (1)
    std::cout << std::format("{:*^25}", point) << '\n';        // (2)
    std::cout << std::format("{:*>25}", point) << '\n';        // (3)

    std::cout << '\n';

    std::cout << std::format("{} {} {}", point.x, point.y, point.z) << '\n';  // (4)
    std::cout << std::format("{0:*<10} {0:*^10} {0:*>10}", point.x) << '\n';  // (5)

    std::cout << '\n';

}

In this case, I derive from the standard formatter std::formatter<std::string>. A std::string_view is also possible. std::formatter<Point> creates the formatted output by calling format on std::formatter. This function call already gets a formatted string as a value. Consequentially, all format specifiers of std::string are applicable (lines 1 – 3). On the contrary, you can also format each value of Point. This is precisely happening in lines (4) and (5).

Internationalization

The formatting functions std::format*, and std::vformat* have overloads accepting a locale. These overloads allow you to localize your format string.

The following code snippet shows the corresponding overload of std::format:

template< class... Args >
std::string format( const std::locale& loc,
                    std::format_string<Args...> fmt, Args&&... args );

To use a given locale, specify L before the type specifier in the format string. Now, you apply the locale in each call of std::format or set it globally with std::locale::global.

In the following example, I explicitly apply the German locale to each std::format call.

// internationalization.cpp

#include <chrono>
#include <exception>
#include <iostream>
#include <thread>

std::locale createLocale(const std::string& localString) {                         // (1)
  try {
    return std::locale{localString};       
  }
  catch (const std::exception& e) {
    return std::locale{""};
  }
}

int main() {

    std::cout << '\n';

    using namespace std::literals;

    std::locale loc = createLocale("de_DE");

    std::cout << "Default locale: " << std::format("{:}", 2023) << '\n';
    std::cout << "German locale:  " << std::format(loc, "{:L}", 2023) << '\n';    // (2)

    std::cout << '\n';

    std::cout << "Default locale: " << std::format("{:}", 2023.05) << '\n';
    std::cout << "German locale:  " << std::format(loc, "{:L}", 2023.05) << '\n'; // (3)

    std::cout << '\n';

    auto start = std::chrono::steady_clock::now();
    std::this_thread::sleep_for(33ms);
    auto end = std::chrono::steady_clock::now();

    const auto duration = end - start;

    std::cout << "Default locale: " << std::format("{:}", duration) << '\n';
    std::cout << "German locale:  " << std::format(loc, "{:L}", duration) << '\n'; // (4)

    std::cout << '\n';

    const auto now = std::chrono::system_clock::now();
    std::cout << "Default locale: " << std::format("{}\n", now);
    std::cout << "German locale:  " << std::format(loc, "{:L}\n", now);            // (5)

    std::cout << '\n';

}

The function createLocale (line 1) creates the German locale. If this fails, it returns the default locale that uses American formatting. I use the German locale in lines (2), (3), (4), and (5). To see the difference, I also applied the std::format calls immediately afterward. Consequentially, the local-dependent thousand separator is used for the integral value (line 2), and the locale-dependent decimal point and thousand separator for the floating-point value (line 3). Accordingly, the time duration (line 4) and the time point (line 5) use the given German locale.

The following screenshot shows the program’s output.

 

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.

     

    What’s Next?

    std::formatter and its specializations also define the format specification for the chrono types. Before I write about them, I will dive deeper into the chrono extension of C++20.

    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,