Mehr Details zur Formatierung benutzerdefinierter Datentypen in C++20
![Schild an Gabelung](https://heise.cloudimg.io/width/700/q75.png-lossy-75.webp-lossy-75.foil1/_www-heise-de_/imgs/18/4/5/4/7/5/7/0/Wende-f585d9b457e26b18.jpeg)
(Bild: Piyawat Nandeenopparit / Shutterstock.com)
Die Implementierung eines Formatierers für einen benutzerdefinierten Datentyp mit mehr als einem Wert in C++20 ist eine Herausforderung.
Dieser Artikel ist der fünfte in meiner Miniserie über Formatierung in C++20. Hier finden sich die vorherigen Beiträge:
- Softwareentwicklung: Die Formatierungsbibliothek in C++20 [1]
- Die Formatierungsbibliothek in C++20: Der Formatstring [2]
- Die Formatierungsbibliothek in C++20: Details zum Formatstring [3]
- Die Formatierungsbibliothek in C++20: Formatieren benutzerdefinierter Datentypen [4]
![](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/4/5/4/7/5/7/0/TimelineCpp20CoreLanguage-e7502c4e54c375e9.png)
Ein Formatierer für mehrere Werte
Point
ist eine Klasse mit drei Mitgliedern.
// 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 diesem Fall leite ich von dem Standardformatierer std::formatter<std::string>
ab. Eine std::string_view
ist ebenfalls möglich. std::formatter<Point>
erzeugt die formatierte Ausgabe durch den Aufruf von format auf std::formatter
. Dieser Funktionsaufruf erhält bereits einen formatierten String als Wert. Folglich sind alle Formatspezifikationen von std::string
anwendbar (1 - 3). Im Gegenteil dazu lässt sich auch jeder Wert von Point
formatieren. Genau das geschieht in (4) und (5).
![](https://heise.cloudimg.io/width/466/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/4/5/4/7/5/7/0/formatPoint-b56d2fc415288bc9.png)
Internationalisierung
Die Formatierungsfunktionen std::format*
und std::vformat*
haben Überladungen, die auch Locals akzeptieren. Mit diesen Überladungen kann man einen Formatierungsstring lokalisieren.
Der folgende Codeschnipsel zeigt die entsprechende Überladung von std::format
:
template< class... Args >
std::string format( const std::locale& loc,
std::format_string<Args...> fmt,
Args&&... args );
Um ein bestimmtes Local zu verwenden, gibt man L
vor dem Datentyp im Formatstring an. Nun wendet man die Locale bei jedem Aufruf von std::format
an oder setzt sie global mit std::locale::global [5]
.
Im folgenden Beispiel wende ich bei jedem std::format-
Aufruf explizit das deutsche Local an.
// 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';
}
Die Funktion createLocale
(1) erstellt das deutsche Local. Wenn dies fehlschlägt, gibt sie das Standardlocal zurück, das die amerikanische Formatierung verwendet. Ich verwende das deutsche Local in (2), (3), (4) und (5). Um den Unterschied zu sehen, habe ich auch die std::format
-Aufrufe direkt im Anschluss daran angewendet. Folglich wird für den ganzzahligen Wert (2) das ortsabhängige Tausendertrennzeichen und für den Fließkommawert (3) das ortsabhängige Dezimalpunkt- und Tausendertrennzeichen verwendet. Dementsprechend verwenden die Zeitdauer (4) und der Zeitpunkt (5) das angegebene deutsche Gebietsschema.
Der folgende Screenshot zeigt die Ausgabe des Programms.
![](https://heise.cloudimg.io/width/475/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/4/5/4/7/5/7/0/internationalization-9e464da3e4d5cf86.png)
Wie geht's weiter?
std::formatter
und seine Spezialisierungen definieren die Formatspezifikation auch für die Datentypen der chrono [6]Bibliothek. Bevor ich über sie schreibe, werde ich tiefer in die Chrono-Erweiterung von C++20 eintauchen.
(rme [7])
URL dieses Artikels:
https://www.heise.de/-9638185
Links in diesem Artikel:
[1] https://www.heise.de/blog/Softwareentwicklung-Die-Formatierungsbibliothek-in-C-20-9602498.html
[2] https://heise.de/-9610546
[3] https://heise.de/-9615593
[4] https://www.heise.de/blog/Die-Formatierungsbibliothek-in-C-20-Formatieren-benutzerdefinierter-Datentypen-9622670.html
[5] https://en.cppreference.com/w/cpp/locale/locale/global
[6] https://en.cppreference.com/w/cpp/chrono
[7] mailto:rme@ix.de
Copyright © 2024 Heise Medien