Softwareentwicklung: Die Formatierungsbibliothek in C++20
Die Serie zur Formatierungsbibliothek in C++20, dessen Basis das C++20-Buch des Blogautors ist, sollen als Nachschlagewerk dienen.
(Bild: Dirtymono/Shutterstock)
- Rainer Grimm
Obwohl Peter Gottschling zwei großartige Blogartikel über die Formatierungsbibliothek in C++20 geschrieben hat ("std::format in C++20", "C++20: std::format um benutzerdefinierte Datentypen erweitern"), werde ich erneut über die Formatierungsbibliothek schreiben. Der Grund dafür ist einfach: Peters Artikel hat eine tolle Einführung und Übersicht gegeben. Ich möchte alle Details vorstellen, damit alle diesen und die kommenden Artikel als Nachschlagewerk nutzen können.
C++20 unterstĂĽtzt die folgenden Formatierungsfunktionen:
Die Funktionen std::format und std::format_to sind funktional äquivalent zu ihren Pendants std::vformat und std::vformat_to, aber sie unterscheiden sich in einigen Punkten:
std::format, std::_format_toundstd::format_to_n: Sie benötigen zur Compile-Zeit einen Wert als Formatstring. Dieser Formatstring kann einconstexpr-String oder ein String-Literal sein.std::vformatundstd::vformat_t:Der Formatstring kann ein lValue sein. Die Argumente müssen an die variadische Funktionstd::make_format_argsübergeben werden, beispielsweise:std::vformat(formatString, std::make_format_args(args)).
Die Formatierungsfunktionen akzeptieren eine beliebige Anzahl von Argumenten. Das folgende Programm vermittelt einen ersten Eindruck von den Funktionen std::format, std::format_to und std::format_to_n.
// format.cpp
#include <format>
#include <iostream>
#include <iterator>
#include <string>
int main() {
std::cout << '\n';
std::cout << std::format("Hello, C++{}!\n", "20")
<< '\n'; // (1)
std::string buffer;
std::format_to( // (2)
std::back_inserter(buffer),
"Hello, C++{}!\n",
"20");
std::cout << buffer << '\n';
buffer.clear();
std::format_to_n( // (3)
std::back_inserter(buffer), 5,
"Hello, C++{}!\n",
"20");
std::cout << buffer << '\n';
std::cout << '\n';
}
Das Programm zeigt in (1) direkt die formatierte Zeichenkette an. Die Aufrufe in den (2) und (3) verwenden jedoch einen String als Puffer. AuĂźerdem schiebt std::format_to_n nur fĂĽnf Zeichen in den Puffer.
Hier ist also das entsprechende Programm, das std::vformat und std::vformat_n verwendet:
// formatRuntime.cpp
#include <format>
#include <iostream>
#include <iterator>
#include <string>
int main() {
std::cout << '\n';
std::string formatString = "Hello, C++{}!\n";
std::cout << std::vformat(formatString,
std::make_format_args("20"))
<< '\n'; // (1)
std::string buffer;
std::vformat_to( // (2)
std::back_inserter(buffer),
formatString,
std::make_format_args("20"));
std::cout << buffer << '\n';
}
Der formatString in den (1) und (2) ist ein lValue.
Vermutlich ist der spannendste Teil der Formatierungsfunktionen der Formatstring ("Hallo, C++{}!\n").
Formatstring
Die Syntax des Formatstrings ist bei den Formatierungsfunktionen std::format, std::format_to, std::format_to_n, std::vformat und std::vformat_to identisch. Ich verwende std::format in meinen Beispielen.
- Syntax: std::format(FormatString, Args)
Der Formatring FormatString besteht aus
- Gewöhnlichen Zeichen (außer { und }),
- Escape-Sequenzen {{ und }}, die durch { und } ersetzt werden sowie
- Ersetzungsfeldern.
Ein Ersatzfeld hat das Format { }.
- Man kann eine Argument-ID und einen Doppelpunkt innerhalb des Ersetzungsfeldes verwenden, gefolgt von einer Formatangabe. Beide Komponenten sind optional.
Mit der Argument-ID lässt sich der Index der Argumente in Args angeben. Die IDs beginnen mit 0. Wenn keine Argument-ID angegeben bst, werden die Felder in der gleichen Reihenfolge ausgefüllt, in der die Argumente angegeben werden. Entweder müssen alle Ersetzungsfelder eine Argument-ID verwenden oder keine; das heißt std::format("{}, {}", "Hallo", "Welt") und std::format("{1}, {0}", "Welt", "Hallo") werden beide kompiliert, aber std::format("{1}, {}", "Welt", "Hallo") nicht.
std::formatter und seine Spezialisierungen definieren die Formatspezifikation fĂĽr die Argumenttypen.
- Basisdatentypen und
std::string: basieren auf der Formatspezifikation von Python. - Chrono-Typen: Ich stelle sie in einem der nächsten Artikel vor.
- Andere formatierbare Datentypen: Benutzerdefinierte
std::formatter-Spezialisierung. Ich werde sie in einem weitern Artikel vorstellen.
Die Tatsache, dass die Formatstrings eine Compile-Zeit-Variable sind, hat zwei interessante Konsequenzen: Performanz und Sicherheit.
Compile-Zeit
- Performanz: Wenn der Formatstring zur Compile-Zeit ĂĽberprĂĽft wird, muss zur Laufzeit nichts getan werden. Folglich versprechen die drei Funktionen
std::format, std::format_toundstd::format_to_neine hervorragende Performanz. Die Prototyp-Bibliothek fmt hat ein paar spannende Benchmarks. - Sicherheit: Die Verwendung eines fehlerhaften Formatstrings zur Compile-Zeit fĂĽhrt zu einem Kompilierungsfehler. Im Gegensatz dazu fĂĽhrt die Verwendung eines Formatstrings zur Laufzeit mit
std::vformatoderstd::vformat_tozu einerstd::format_error-Ausnahme.
Wie geht's weiter?
Im nächsten Blogartikel werde ich die Theorie mit der Praxis ergänzen.
(rme)