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.

In Pocket speichern vorlesen Druckansicht 9 Kommentare lesen
Schreibmaschine

(Bild: Dirtymono/Shutterstock)

Lesezeit: 4 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

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.

Modernes C++ – Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tätig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und häufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschäftigt er sich intensiv mit seiner Leidenschaft C++.

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_to und std::format_to_n: Sie benötigen zur Compile-Zeit einen Wert als Formatstring. Dieser Formatstring kann ein constexpr-String oder ein String-Literal sein.
  • std::vformat und std::vformat_t: Der Formatstring kann ein lValue sein. Die Argumente müssen an die variadische Funktion std::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").

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.

  • 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_to und std::format_to_n eine 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::vformat oder std::vformat_to zu einer std::format_error-Ausnahme.

Im nächsten Blogartikel werde ich die Theorie mit der Praxis ergänzen.

(rme)