Ein std::expected<R,E>
enthält entweder das Ergebnis einer Berechnung (R
= Resultat) oder eine Angabe, warum die Berechnung scheiterte (E
= Error):
#include <expected> #include <print> #include <string> std::expected<int, std::string> question(int n) { if (n == 42) return n; else return std::unexpected{ n < 42 ? "zu klein" : "zu groß" }; } int main() { for (int n : { 6, 7, 8, 9 }) { auto answer = question(6*n); if (answer.has_value()) // oder: if(answer) std::println("Die richtige Antwort ist {}", answer.value()); else std::println("Fehler: {}", answer.error()); } }
has_value() | Ist Wert vorhanden? |
value() | erwarteter Wert |
error() | unerwarteter Wert |
value_or(x) | Alternative, falls erwarteter Wert nicht vorhanden |
error_or(e) | Alternative, falls erwarteter Wert vorhanden |
Monad: a monoid in the category of endofunctors
– James Iry
Once you understand, you lose the ability to explain.
– https://news.ycombinator.com/item?id=22417583
Diese rufen in Abhängigkeit vom Vorhandensein eines Wertes eine Funktion f
auf.
Im Ergebnis entsteht wiederum ein std::expected<_,_>
.
Dabei werden die Typen R
und E
entsprechend angepasst.
Wert vorhanden? | ja | nein | Rückgabetyp von f |
---|---|---|---|
and_then(f) | f(value()) | std::expected<R2,E> |
|
or_else(f) | f(error()) | std::expected<R,E2> |
|
transform(f) | f(value()) | R2 |
|
transform_error(f) | f(error()) | E2 |
Mit den monadischen Operationen lassen sich Verarbeitungsschritte und eventuell auftretende Fehler verketten:
#include <expected> #include <iostream> #include <print> #include <string> struct Date { int y, m, d; }; auto leapyear(int y) { return (y % 4 == 0 && !(y % 100 == 0)) || y % 400 == 0; } auto day_of_week(Date date) // Zellers Kongruenz, Gregorianisch: y > 1582 { auto [y,m,d] = date; if (m < 3) { m += 12; --y; } auto C = y / 100; auto D = y % 100; return (d + 13*(m+1)/5 + D + D/4 + C/4 + 5*C) % 7; } auto name_of_weekday(int w) { std::string const name[] = { "Sa", "So", "Mo", "Di", "Mi", "Do", "Fr" }; return name[w]; } std::expected<Date, std::string> input() { Date date; char sep; std::cout << "Datum (y-m-d): "; if (std::cin >> date.y >> sep >> date.m >> sep >> date.d && sep == '-') return date; else return std::unexpected{"falsche Eingabe"}; } std::expected<Date, std::string> valid(Date date) { if (date.m < 1 || date.m > 12) return std::unexpected{"ungültiger Monat"}; if (date.d < 1 || date.d > 31) return std::unexpected{"ungültiger Tag"}; for (auto m : { 4, 6, 9, 11 }) { if (m == date.m && date.d == 31) return std::unexpected{"April, Juni, September und Oktober haben keinen 31. Tag"}; } if (date.m == 2 && date.d > 28 + leapyear(date.y)) { return std::unexpected{"Diesen Tag gibt es im Februar nicht"}; } return date; } int main() { auto result = input() .and_then(valid) .transform(day_of_week) .transform(name_of_weekday) .transform_error([](std::string s) { return "Fehler: " + s; }) .or_else([](const std::string& error) { std::println(std::cerr, "Eintrag {}", error); return std::expected<std::string, std::string>{std::unexpected{error}}; }); if (result) std::println("{}", result.value()); else std::println("{}", result.error()); }
Siehe auch: <optional>, <variant>