std::expected în C++

Rezultatul unei operații care poate eșua: conține fie valoarea (T), fie motivul erorii (E).

Sintaxă
#include <expected>
std::expected<T, E> rezultat = valoare;
std::expected<T, E> eroare = std::unexpected(e);

Ce face std::expected

std::expected<T, E> este ca un std::optional care, în loc de „nimic”, spune de ce lipsește valoarea. Funcția returnează fie valoarea, fie std::unexpected(eroare).

Apelantul verifică cu if (r) sau r.has_value(), apoi citește *r sau r.value(); eroarea se citește cu r.error(). Există și value_or, and_then, transform, ca la optional.

Este o alternativă la excepții pentru erorile previzibile (date invalide, fișier lipsă), în care vrei ca apelantul să fie obligat să trateze eșecul.

Ce returnează

Un obiect care conține fie un T, fie un E. std::unexpected(e) construiește partea de eroare.

Exemple

Exemplul 1: Împărțire cu mesaj de eroare

#include <expected>
#include <print>
#include <string>

std::expected<double, std::string> imparte(double a, double b) {
    if (b == 0) {
        return std::unexpected("împărțire la zero");
    }
    return a / b;
}

int main() {
    for (double b : {4.0, 0.0}) {
        auto r = imparte(10, b);
        if (r) {
            std::println("10 / {} = {}", b, *r);
        } else {
            std::println("Eroare: {}", r.error());
        }
    }
}

Rezultat

10 / 4 = 2.5
Eroare: împărțire la zero

Exemplul 2: Coduri de eroare cu enum

#include <expected>
#include <print>
#include <string>

enum class EroareCont { SoldInsuficient, ContBlocat };

std::expected<int, EroareCont> retrage(int sold, int suma, bool blocat) {
    if (blocat) return std::unexpected(EroareCont::ContBlocat);
    if (suma > sold) return std::unexpected(EroareCont::SoldInsuficient);
    return sold - suma;
}

std::string text(EroareCont e) {
    return e == EroareCont::ContBlocat ? "cont blocat" : "sold insuficient";
}

int main() {
    auto a = retrage(500, 200, false);
    auto b = retrage(500, 900, false);
    auto c = retrage(500, 100, true);

    std::println("a: sold rămas {}", a.value());
    std::println("b: {}", text(b.error()));
    std::println("c: {}", c.has_value() ? "ok" : text(c.error()));
    std::println("b sau 0: {}", b.value_or(0));
}

Rezultat

a: sold rămas 300
b: sold insuficient
c: cont blocat
b sau 0: 0

Greșeli frecvente

  • Apelezi r.error() când r conține o valoare (sau *r când conține eroare). Este comportament nedefinit; verifică întâi cu if (r).
  • Returnezi eroarea direct (return "mesaj";) fără std::unexpected. Compilatorul încearcă să o transforme în valoarea T; uneori nici nu dă eroare, ci produce un rezultat greșit.

Comenzi înrudite

  • std::optional: O valoare care poate lipsi: conține fie un obiect de tip T, fie nimic (std::nullopt).
  • std::optional::and_then: Înlănțuiește operații pe un optional: and_then și transform rulează funcția doar dacă există valoare.
  • std::variant: O valoare care are, la un moment dat, exact unul dintre mai multe tipuri posibile (union sigur).
  • try, catch: try marchează codul care poate arunca o excepție, iar blocurile catch prind și tratează excepțiile de un anumit tip.