Programmiersprache C++23: Was sonst noch Interessantes im Standard dabei ist
Ich habe bereits 10 Artikel über C++23 geschrieben. Heute möchte ich auf die Features eingehen, die ich in der ersten Runde vergessen habe.​

(Bild: SergioVas/Shutterstock)
- Rainer Grimm
C++23 hat einige wichtige Features, aber auch bei den Details gibt es spannende Ergänzungen.
Zur Erinnerung: Hier sind die wichtigsten Features von C++23.
Die wichtigsten C++23-Features
C++23 bietet mit Deducing this ein kleines, aber sehr wirkungsvolles Feature der Kernsprache. Deducing this ermöglicht es, ähnlich wie in Python den implizit übergebenen Zeiger in einer Memberfunktionsdefinition explizit zu machen. Dank dieser Ableitung werden einige komplexe Techniken in C++, wie CRTP oder das Overload Pattern, zu einem Kinderspiel.
Die C++23-Bibliothek erhält viele wichtige Ergänzungen. Man kann die Standardbibliothek direkt mit import std;
importieren oder die C++20-Formatstrings in std::print
und std::println
anwenden. AuĂźerdem erhalten wir aus GrĂĽnden der Performanz flache assoziative Container wie std::flat_map. std::flap_map
ist ein Drop-in-Ersatz fĂĽr std::map. std::optional
wird aus Gründen der Kompatibilität um eine monadische Schnittstelle erweitert. Der neue Datentyp std::expected
hat bereits eine komponierbare Schnittstelle und kann einen erwarteten oder einen unerwarteten Wert zur Fehlerbehandlung speichern. Dank std::mdspan
erhalten wir eine multidimensionale Span. std::generator
schlieĂźlich ist die erste konkrete Coroutine zur Erzeugung eines Zahlenstroms. std::generator
ist Teil der ranges-Bibliothek, die in C++23 ebenfalls verbessert wird.
Mehr Details findet sich in meinen frĂĽheren Artikel:
Kernsprache
- C++23: Deducing This erstellt explizite Zeiger
- C++23: Syntactic Sugar with Deducing This
- C++23: Die kleinen Perlen in der Kernsprache
- C++23: Mehr kleine Perlen in der Kernsprache
Bibliothek
- C++23: Ein modularisierte Standardbibliothek und zwei neue Funktionen
- Ranges: Verbesserungen mit C++23
- C++23: Eine neue Art der Fehlerbehandlung mit std::expected
- C++23: Vier neue assoziative Container
- C++23: Eine multidemensionale View
Doch das ist noch nicht alles, was C++23 zu bieten hat.
FlieĂźkommatypen mit fester Breite
Bislang unterstĂĽtzt C++ die folgenden Datentypen:
- kein Suffix: double
- f oder F Suffix: float
- l oder L Suffix: long double
C++23 bringt fĂĽnf neue Literal-Suffixe. Die Tabelle auf cppreference zeigt ihre Datentypen und ihre Eigenschaften:
Die neuen Datentypen sind in de Header <stdfloat>
definiert. Mit einem vordefinierten Makro lässt sich prüfen, ob die eigene Implementierung die neuen Datentypen unterstützt.
Stacktrace
Die Analyse des aktuellen Stacks ist oft sehr nĂĽtzlich fĂĽr die Fehlersuche. Die neue Stacktrace-Bibliothk bietet dies genau an.
Die Bibliothek besteht aus zwei Klassen:
stacktrace_entry:
Darstellung einer Auswertung in einem Stacktracebasic_stacktrace:
ein Schnappschuss des gesamten Stacktrace oder eines bestimmten Teils
In einfachen Worten ausgedrĂĽckt: Mit der Klasse stacktrace_entry
kann man Informationen ĂĽber eine Auswertung in einem Stacktrace erhalten. Jedes stacktrace_entry
-Objekt ist entweder leer oder repräsentiert eine Auswertung in einem Stacktrace.
Die Klasse basic_stacktrace
ist ein Schnappschuss des gesamten Stacktrace oder eines bestimmten Teils davon.
Immer noch verwirrend?
Im folgenden einfachen Beispiel ruft die main
Funktion die Funktion func1
auf, func1
ruft func2
auf und func2
ruft func3
auf.
// stacktrace1.cpp
#include <iostream>
#include <stacktrace>
void func3() {
std::cout << std::stacktrace::current() << '\n';
}
void func2() {
func3();
}
void func1() {
func2();
}
int main() {
func1();
}
func3
ruft die statische Funktion current
auf std::stacktrace
auf. std::stacktrace
ist ein Alias fĂĽr std::basic_stacktrace
mit dem Standard-Allokator.
Das Programm gibt beim AusfĂĽhren Informationen ĂĽber den Stacktrace.
Das Kompilieren des Programms mit dem Compiler Explorer war eine ziemliche Herausforderung. Es hat eine Weile gedauert, bis ich die passende GCC-Version und die Bibliotheken herausgefunden hatte.
Hier ist die Befehlszeile fĂĽr g++ 13.1: g++ -std=c++23 -lstdc++_libbacktrace
.
Dank des std::stacktrace_entry
lässt sich der Stacktrace abfragen.
// stacktrace2.cpp
#include <iostream>
#include <stacktrace>
void func3() {
auto stacktrace = std::stacktrace::current();
for (const auto& entry: stacktrace) {
std::cout << "Description: " << entry.description() << '\n';
std::cout << "file: " << entry.source_file()
<< " and line: " << entry.source_line() <<'\n';
std::cout << '\n';
}
}
void func2() {
func3();
}
void func1() {
func2();
}
int main() {
func1();
}
In der Funktion func3
durchlaufe ich die Stack-Trace-Einträge und zeige explizit ihre Beschreibung, Datei und Zeile an.
Ăśber die ĂĽbrigen Features von C++23 werde ich nur ein paar Worte schreiben. Die Beispiele stammen von cppreference.com.
Beginnen möchte ich mit zwei Features, die sich auf die Performanz eines Programms auswirken können.
std::unreachable
Man kann std::unreachable
verwenden, um Code zu markieren, der nicht erreicht werden kann. Der Aufruf von std::unreachable ergibt undefined behavior.
// from https://en.cppreference.com/w/cpp/utility/unreachable
switch (xy)
{
case 128: [[fallthrough]];
case 256: [[fallthrough]];
case 512: /* ... */
tex.clear();
tex.resize(xy * xy, Color{0, 0, 0, 0});
break;
default:
std::unreachable();
}
std::move_only_function
Mit C++11 gibt es std::function. std::function
ist ein polymorpher Funktionswrapper und kann sie beliebige Callables annehmen und ihnen einen Namen geben. Callables sind alle Entitäten, die sich wie Funktionen verhalten, wie Funktionen, Funktionsobjekte oder Lambdas.
Mehr ĂĽber std::function
erfährst du hier: Funktional in TR1 und C++11.
Im Gegensatz zu std::function
kann std::move_only_function
nur ein konstruierbares Callable bekommen. Das Callable-Objekt kann in der std::move_only_function
gespeichert werden, um zusätzliche Speicheranforderungen zu vermeiden.
// from https://en.cppreference.com/w/cpp/utility/functional/move_only_function
auto lambda = [task = std::move(packaged_task)]() mutable { task(); };
// std::function<void()> function = std::move(lambda); // Error
std::move_only_function<void()> function = std::move(lambda); // OK
std::byteswap
std::byteswap
vertauscht die Bytes im einem Wert n. n
muss integral sein.
Das folgende Programm wendet std::byteswap
an.
// from https://en.cppreference.com/w/cpp/numeric/byteswap
#include <bit>
#include <concepts>
#include <cstdint>
#include <iomanip>
#include <iostream>
template<std::integral T>
void dump(T v, char term = '\n')
{
std::cout << std::hex << std::uppercase << std::setfill('0')
<< std::setw(sizeof(T) * 2) << v << " : ";
for (std::size_t i{}; i != sizeof(T); ++i, v >>= 8)
std::cout << std::setw(2)
<< static_cast<unsigned>(T(0xFF) & v) << ' ';
std::cout << std::dec << term;
}
int main()
{
static_assert(std::byteswap('a') == 'a');
std::cout << "byteswap for U16:\n";
constexpr auto x = std::uint16_t(0xCAFE);
dump(x);
dump(std::byteswap(x));
std::cout << "\nbyteswap for U32:\n";
constexpr auto y = std::uint32_t(0xDEADBEEFu);
dump(y);
dump(std::byteswap(y));
std::cout << "\nbyteswap for U64:\n";
constexpr auto z = std::uint64_t{0x0123456789ABCDEFull};
dump(z);
dump(std::byteswap(z));
}
Zum Schluss ist hier die Ausgabe des Programms.
Wie geht's weiter?
In meinem nächsten Artikel gehe ich auf C++26 ein. (rme)