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:
- The Formatting Library in C++20
- The Formatting Library in C++20: The Format String
- The Formatting Library in C++20: The Format String (2)
- Formatting User-Defined Types in C++20
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.
Modernes C++ Mentoring
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)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,