C++23 markiert einen gewaltigen Sprung nach vorn in Sachen Alltagsergonomie, sicherer Fehlerweitergabe und Compile-Time-Tooling. Über die mit C++20 eingeführten grundlegenden Paradigmen hinaus konzentriert sich dieser Standard stark darauf, Codebases sauberer, schneller und ausdrucksstärker zu machen.
Hier sind die 15 wichtigsten Features, die jeder C++-Entwickler in modernen Projekten nutzen sollte.
1. std::print und std::println
Formatierte Ausgabe ist endlich ein nativer, typsicherer Einzeiler in der Standardbibliothek. Schluss mit dem Jonglieren alter printf-Formatierer oder klobiger iostreams.
#include <print>
int main() {
std::println("Hello, {}! Codebase health is: {:.1f}%", "C++23", 98.5);
}
- Warum es wichtig ist: zur Compile-Zeit geprüfte Formatstrings, höherer Durchsatz und saubereres Logging auf Hot Paths.
2. std::expected
Bringt typisierte Errors-as-Values nach C++: Funktionen können entweder ein gültiges Ergebnis oder eine unerwartete Fehler-Payload zurückgeben — ganz ohne Exceptions.
#include <expected>
#include <string>
std::expected<int, std::string> parseConfig(const std::string& path) {
if (path.empty()) return std::unexpected("Invalid path");
return 42; // parsed value
}
- Warum es wichtig ist: ideal für latenzkritische Module, Plugin-Schnittstellen und ABI-Grenzen, bei denen Exceptions Overhead oder Sicherheitsrisiken verursachen.
3. Deducing this (expliziter Objektparameter)
Löst ein seit einem Jahrzehnt bestehendes Problem für Bibliotheksautoren: Member-Funktionen können jetzt ihre eigene Wertkategorie und Const-ness deduzieren.
struct Builder {
std::string buffer;
auto append(this auto&& self, std::string_view text) {
self.buffer.append(text);
return std::forward<decltype(self)>(self); // Fluent chaining
}
};
- Warum es wichtig ist: eliminiert doppelte Overloads für const-, non-const-, lvalue- und rvalue-Member-Funktionen.
4. Ranges erwachsen geworden: std::views::zip, chunk und slide
C++20 führte Ranges ein, aber C++23 rüstet sie mit Multi-Sequenz-Adaptern auf.
#include <ranges>
#include <vector>
#include <iostream>
std::vector<int> keys = {1, 2, 3};
std::vector<std::string> values = {"one", "two", "three"};
auto zipped = std::views::zip(keys, values);
- Warum es wichtig ist: ersetzt fehleranfällige indexbasierte Schleifen durch korrekte-by-Construction-Datenpipelines.
5. std::ranges::to
Eine elegante Möglichkeit, jede Range-View direkt zurück in einen Standardcontainer zu konvertieren.
#include <ranges>
#include <vector>
#include <set>
std::vector<int> v = {1, 2, 2, 3};
auto s = v | std::views::filter([](int x){ return x > 1; })
| std::ranges::to<std::set>();
- Warum es wichtig ist: beendet Lazy-Evaluation-Pipelines sauber, ohne manuellen Container-Initialisierungs-Boilerplate.
6. std::stacktrace
Standardisierte portable Stacktraces, direkt einsatzbereit.
#include <stacktrace>
#include <print>
void debugLog() {
std::println("Current execution path:\n{}", std::stacktrace::current());
}
- Warum es wichtig ist: beschleunigt Incident-Triage und Fehlerberichte erheblich — ohne plattformspezifische APIs (DBGHELP / backtrace).
7. Monadische Operationen für std::optional
std::optional erhält and_then, transform und or_else — mit der Ausdruckskraft von std::expected.
#include <optional>
std::optional<std::string> getUsername(int id);
auto length = getUsername(10)
.transform([](const std::string& name) { return name.length(); });
- Warum es wichtig ist: beseitigt verschachtelte if (opt.has_value())-Pyramiden vollständig.
8. if consteval
Unterscheidet sauber zwischen Compile-Zeit-Auswertung und Laufzeit-Ausführung.
constexpr int compute(int x) {
if consteval {
return fastCompileTimeAlgorithm(x);
} else {
return optimizedRuntimeSIMD(x);
}
}
- Warum es wichtig ist: vereinfacht Funktionen mit zwei Pfaden, die sowohl für Compile-Zeit-Auswertung als auch für Laufzeit-Performance optimiert sind.
9. auto(x) und auto{x} (decayierte Ausdrücke)
Erzeugt inline eine temporäre, decayierte Kopie eines Ausdrucks.
auto process = [ptr = auto(*expensiveRef)]() {
// captures a decayed copy safely
};
- Warum es wichtig ist: verhindert hängende Referenzen in Lambdas und an Grenzen asynchroner Tasks.
10. std::generator (Coroutinen werden praxistauglich)
Stellt einen standardisierten asynchronen/lazy Generatortyp für C++-Coroutinen bereit.
#include <generator>
std::generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
auto next = a + b;
a = b;
b = next;
}
}
- Warum es wichtig ist: vereinfacht Stream-Verarbeitung und Sequenzgenerierung ohne schweren eigenen Boilerplate-Code.
11. std::byteswap
Ein portables Standard-Utility zum Vertauschen der Byte-Reihenfolge.
#include <bit>
uint32_t val = 0x12345678;
uint32_t swapped = std::byteswap(val); // 0x78563412
- Warum es wichtig ist: essenziell für Netzwerkprotokolle, das Parsen binärer Dateien und plattformübergreifende Low-Level-Serialisierung.
12. std::to_underlying
Konvertiert einen Enumerationstyp sicher direkt in seinen zugrunde liegenden Typ.
enum class Status : uint8_t { Ok = 0, Error = 1 };
auto val = std::to_underlying(Status::Error); // returns uint8_t(1)
- Warum es wichtig ist: ersetzt umständliche explizite Casts (static_cast<std::underlying_type_t<Status>>(val)) durch klare Absicht.
13. std::string::contains und std::string_view::contains
Prüft direkt das Vorkommen eines Teilstrings oder Zeichens.
#include <string>
std::string url = "https://cppdepend.com";
if (url.contains("https")) {
// Secure URL handled cleanly
}
- Warum es wichtig ist: eliminiert das ausführliche Idiom str.find(...) != std::string::npos.
14. Mehrdimensionales std::mdspan
Eine nicht-besitzende mehrdimensionale View über zusammenhängende Datenblöcke.
#include <mdspan>
#include <vector>
std::vector<double> data(12);
std::mdspan matrix(data.data(), 3, 4); // 3x4 grid view
matrix[1, 2] = 5.0; // Zero allocation overhead
- Warum es wichtig ist: perfekt für rechenintensive Verarbeitung, Bildbearbeitung und Game Engines — ohne das Speicherlayout vorzuschreiben.
15. std::unreachable (explizites Markieren unmöglicher Pfade)
Ermöglicht das Markieren unerreichbarer Zweige, damit der Compiler aggressiv optimieren kann.
#include <utility>
void handleColor(Color c) {
switch(c) {
case Color::Red: break;
case Color::Blue: break;
default: std::unreachable(); // Tells optimizer this path is impossible
}
}
- Warum es wichtig ist: schaltet fortgeschrittene Compiler-Branch-Optimierungen frei und eliminiert tote Laufzeit-Prüfpfade.
Halten Sie Ihre modernisierte Codebase gesund
Die Einführung von C++23-Features modernisiert Ihre Codebase — aber Modernisierung im großen Stil braucht Leitplanken. Welche Dateien nutzen noch printf-artiges Logging? Hat die neue std::expected-Grenze eine unbeabsichtigte architektonische Abhängigkeit eingeführt? Steigt die zyklomatische Komplexität in Ihrem neuen Coroutinen-Code?
CppDepend beantwortet diese Fragen automatisch: Durchsuchen Sie Ihre Codebase mit CQLinq nach jedem zu migrierenden Legacy-Pattern, verfolgen Sie technische Schulden während der Modernisierung und visualisieren Sie die Auswirkungen jeder Änderung in 3D mit Smart Code City — wie in unseren vorherigen Artikeln gezeigt:
