Mehr Details zur Formatierung benutzerdefinierter Datentypen in C++20
Die Implementierung eines Formatierers fĂĽr einen benutzerdefinierten Datentyp mit mehr als einem Wert in C++20 ist eine Herausforderung.

(Bild: Piyawat Nandeenopparit / Shutterstock.com)
- Rainer Grimm
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
- Die Formatierungsbibliothek in C++20: Der Formatstring
- Die Formatierungsbibliothek in C++20: Details zum Formatstring
- Die Formatierungsbibliothek in C++20: Formatieren benutzerdefinierter Datentypen
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).
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
.
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.
Wie geht's weiter?
std::formatter
und seine Spezialisierungen definieren die Formatspezifikation auch fĂĽr die Datentypen der chrono Bibliothek. Bevor ich ĂĽber sie schreibe, werde ich tiefer in die Chrono-Erweiterung von C++20 eintauchen.
(rme)