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

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • 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

    Seminars

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

    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++
    • Clean Code with Modern C++
    • C++20

    Online Seminars (German)

    Contact Me

    Modernes C++ Mentoring,