Vorschau auf C++23 & C++26
Erkunde std::expected, std::print, Deducing this und die Ranges-Verbesserungen aus C++23 und wirf einen Blick auf kommende C++26-Features wie Contracts, Reflection und Pattern Matching.
Die Evolution geht weiter
C++23 ist ein "Vervollständigungs"-Release, das von C++20 gelassene Lücken schließt und vielfach gewünschte Features nachliefert. std::expected bringt Fehlerbehandlung im Rust-Stil, std::print liefert zeitgemäße Ausgabe, Deducing this ermöglicht mächtige Metaprogrammierungsmuster, und Ranges bekommen Dutzende neuer Views. Parallel dazu entwickelt sich C++26 zu einem weiteren großen Release mit Contracts, Reflection und Pattern Matching — Features, die grundlegend verändern werden, wie wir sicheren, ausdrucksstarken C++-Code schreiben.
std::expected: Moderne Fehlerbehandlung
std::expected enthält entweder einen Erfolgswert vom Typ T oder einen Fehler vom Typ E. In Kombination mit monadischen Operationen (.and_then(), .transform(), .or_else()) ermöglicht es komponierbare Fehlerbehandlung ohne Exceptions:
#include <iostream>
#include <expected>
#include <string>
#include <charconv>
#include <system_error>
enum class ParseError {
empty_input,
invalid_format,
out_of_range
};
std::expected<int, ParseError> parse_int(std::string_view sv) {
if (sv.empty()) return std::unexpected(ParseError::empty_input);
int result{};
auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), result);
if (ec == std::errc::invalid_argument)
return std::unexpected(ParseError::invalid_format);
if (ec == std::errc::result_out_of_range)
return std::unexpected(ParseError::out_of_range);
if (ptr != sv.data() + sv.size())
return std::unexpected(ParseError::invalid_format);
return result;
}
std::expected<int, ParseError> double_if_positive(int val) {
if (val <= 0) return std::unexpected(ParseError::out_of_range);
return val * 2;
}
int main() {
// Direct usage
auto result = parse_int("42");
if (result) {
std::cout << "Parsed: " << *result << "\n";
} else {
std::cout << "Error code: " << static_cast<int>(result.error()) << "\n";
}
// Monadic chaining — like Rust's Result::and_then
auto chained = parse_int("21")
.and_then(double_if_positive) // int -> expected<int, E>
.transform([](int v) { return v + 1; }) // int -> int (wrapped)
.or_else([](ParseError e) -> std::expected<int, ParseError> {
std::cout << "Recovering from error\n";
return 0; // default value on error
});
std::cout << "Chained result: " << *chained << "\n"; // 43
return 0;
}std::print und std::println
std::print und std::println kombinieren std::format mit der Ausgabe und ersetzen std::cout << in den meisten Anwendungsfällen. Sie sind schneller, typsicher und ergeben lesbaren Code:
#include <print>
#include <vector>
#include <string>
#include <ranges>
int main() {
// Basic printing — no more << chains!
std::println("Hello, {}!", "C++23");
std::print("No newline here. ");
std::println("But here.");
// Formatting works just like std::format
int x = 42;
double pi = 3.14159;
std::println("x = {}, pi = {:.2f}", x, pi);
std::println("Hex: {:#x}, Binary: {:#b}", 255, 255);
// Print to stderr
std::println(stderr, "Warning: something happened");
// Containers and ranges (C++23 formatter support)
std::vector<int> v{1, 2, 3, 4, 5};
std::println("Vector: {}", v); // [1, 2, 3, 4, 5]
// Formatted table
std::println("{:<15} {:>10} {:>10}", "Name", "Score", "Grade");
std::println("{:<15} {:>10} {:>10}", "Alice", 95, "A");
std::println("{:<15} {:>10} {:>10}", "Bob", 87, "B+");
return 0;
}Ranges-Verbesserungen: zip, chunk, slide
C++23 ergänzt mächtige neue Range-Views, die gängige Muster abdecken, für die bisher manuelle Schleifen oder Fremdbibliotheken nötig waren:
#include <print>
#include <ranges>
#include <vector>
#include <string>
int main() {
std::vector<std::string> names{"Alice", "Bob", "Carol"};
std::vector<int> scores{95, 87, 92};
// zip — combine multiple ranges element-wise
for (auto [name, score] : std::views::zip(names, scores)) {
std::println("{}: {}", name, score);
}
std::vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9};
// chunk — split into groups of N
for (auto chunk : data | std::views::chunk(3)) {
std::print("[ ");
for (int v : chunk) std::print("{} ", v);
std::println("]");
}
// [ 1 2 3 ] [ 4 5 6 ] [ 7 8 9 ]
// slide — sliding window of size N
for (auto window : data | std::views::slide(3)) {
std::print("( ");
for (int v : window) std::print("{} ", v);
std::println(")");
}
// ( 1 2 3 ) ( 2 3 4 ) ( 3 4 5 ) ... ( 7 8 9 )
// cartesian_product — all combinations
std::vector<char> suits{'H', 'D', 'C', 'S'};
std::vector<int> ranks{1, 2, 3};
for (auto [suit, rank] : std::views::cartesian_product(suits, ranks)) {
std::print("{}{} ", suit, rank);
}
std::println("");
// enumerate — index + value (finally!)
for (auto [i, name] : std::views::enumerate(names)) {
std::println("[{}] {}", i, name);
}
return 0;
}Deducing this
Mit Deducing this (auch "expliziter Objektparameter" genannt) können Memberfunktionen in C++23 this als expliziten, hergeleiteten Parameter entgegennehmen. Damit entfällt die Notwendigkeit, const- und Nicht-const-Überladungen zu duplizieren, rekursive Lambdas werden möglich und CRTP wird einfacher:
struct Widget {
std::string name;
// Vor C++23: zwei identische Überladungen
// const std::string& get_name() const { return name; }
// std::string& get_name() { return name; }
// C++23: eine Funktion deckt beides ab
template<typename Self>
auto&& get_name(this Self&& self) {
return std::forward<Self>(self).name;
}
};Der Parameter this Self&& self leitet her, ob das Objekt const, nicht-const, ein Lvalue oder ein Rvalue ist — und leitet korrekt weiter. Dieses Muster, das "Deducing this"-Idiom, kann Überladungssätze von Memberfunktionen halbieren.
C++26-Vorschau: Contracts, Reflection, Pattern Matching
C++26 wird voraussichtlich mehrere umwälzende Features enthalten:
Contracts — pre, post und contract_assert erlauben Vorbedingungen, Nachbedingungen und Zusicherungen, die das Build-System aktivieren oder deaktivieren kann:
int sqrt_int(int x)
pre(x >= 0)
post(r: r * r <= x)
{
// Implementierung
}Statische Reflection — Typen, Member und Enumeratoren zur Kompilierzeit inspizieren. Code auf Basis von Struct-Feldern generieren, Serialisierung automatisch ableiten und mehr — ganz ohne Makros.
Pattern Matching — inspect-Ausdrücke erlauben das Matchen auf Typen, Werte und Struktur:
inspect (variant_value) {
<int> i => std::println("int: {}", i);
<string> s => std::println("string: {}", s);
__ => std::println("other");
};Sender/Receiver (std::execution) — Ein standardisiertes Framework für asynchrone Ausführung, das strukturierte Nebenläufigkeit bietet und improvisierte Thread-Pool-Implementierungen ablöst.
So bleibst du bei der Entwicklung von C++ auf dem Laufenden:
1. Lege einen Mindeststandard fest, den dein Projekt anpeilt (C++17 ist heute für den meisten Produktivcode eine solide Basis)
2. Wähle den Standard über Compiler-Flags: -std=c++17, -std=c++20, -std=c++23
3. Prüfe die Compiler-Unterstützung auf cppreference.com/compiler_support — nicht jeder Compiler implementiert jedes Feature
4. Nutze Feature-Test-Makros wie __cpp_concepts oder __cpp_lib_expected, um neuere Features bedingt einzusetzen
5. Lies die Proposals (wg21.link/pXXXX) zu den Features, die du einführen willst
6. Jage nicht jedem neuen Feature hinterher — übernimm, was deine Codebasis einfacher und korrekter macht, nicht bloß neuer
Die neuesten Standard-Features zu früh einzusetzen, hat handfeste Kosten:
Compiler-Bugs: Neue Features haben oft Implementierungsfehler, die erst im Produktiveinsatz auffallen. C++20-Module und -Coroutines etwa hatten noch 2 bis 3 Jahre nach der Standardisierung erhebliche Compiler-Bugs.
Unfertiges Tooling: Debugger, Profiler und statische Analysewerkzeuge unterstützen die neuesten Features möglicherweise nicht vollständig. Die IDE-Unterstützung (IntelliSense, Code-Vervollständigung) hinkt häufig hinterher.
Portabilität: Wenn dein Code auf mehreren Plattformen kompilieren muss (Linux, macOS, Windows, Embedded), bist du auf die Schnittmenge dessen beschränkt, was alle Zielcompiler unterstützen.
Bereitschaft im Team: Neue Features erfordern, dass das ganze Team dazulernt. Ein Feature, das nur eine Entwicklerin versteht, wird zum Wartungsengpass.
std::expectedermöglicht Fehlerbehandlung im Rust-Stil mit monadischer Verkettung (.and_then(),.transform())std::print/std::printlnersetzenstd::cout <<durch saubere, schnelle, typsichere formatierte Ausgabe- C++23-Ranges ergänzen die Views
zip,chunk,slide,cartesian_productundenumerate - Deducing this beseitigt die Duplizierung von const- und Nicht-const-Überladungen bei Memberfunktionen
- C++26 peilt Contracts, statische Reflection, Pattern Matching und
std::executionfür Asynchronität an - Führe neue Standards schrittweise ein — prüfe die Compiler-Unterstützung, teste gründlich und achte auf die Bereitschaft des Teams
Quiz — Teste dein Wissen
(15 XP)1. Wofür steht `std::expected<int, Error>`?
2. Was erzeugt `std::views::zip(names, scores)`?
3. Welches C++26-Feature erlaubt es, Vor- und Nachbedingungen von Funktionen anzugeben?