Inhaltsverzeichnis

<expected>

Ein std::expected<R,E> enthält entweder das Ergebnis einer Berechnung (R = Resultat) oder eine Angabe, warum die Berechnung scheiterte (E = Error):

expected.cpp
#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());
  }
}

Methoden

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

Monadische Operationen

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:

name_of_weekday.cpp
#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>