std::optional<T> (C++17) represents a value that might not be present, replacing the older pattern of a special sentinel value or a raw pointer that might be null. std::variant<Types...> (C++17) is a type-safe union — unlike C's raw union, it tracks which type is actually active and throws if accessed incorrectly, rather than silently reinterpreting bytes. std::expected<T, E>, added in C++23, lets a function return either a successful value or a specific error type explicitly in its signature, without needing an exception or an error-code out-parameter — directly comparable to Rust's Result or TypeScript's discriminated unions for error handling.
std::optional: explicit absence, since C++17
has_value() and the * operator (or value()) are the standard way to check and unwrap — the type signature itself documents that findUser might legitimately have nothing to return.
std::optional<std::string> findUser(int id) {
if (id == 1) return "Alice";
return std::nullopt; // explicitly "no value"
}
auto result = findUser(2);
if (result.has_value()) {
std::cout << *result << "\n";
} else {
std::cout << "not found\n";
}std::variant: a genuinely type-safe union
Unlike C's raw union, accessing a variant as the WRONG currently-active type throws std::bad_variant_access rather than silently reinterpreting bytes — std::visit is the idiomatic way to handle every possible alternative safely.
std::variant<int, std::string> value = 42;
std::visit([](auto&& v) {
std::cout << v << "\n"; // works for whichever alternative is currently active
}, value);
value = "hello"; // now holds a string insteadstd::expected: explicit error handling, since C++23
The function's own signature documents both the success type AND the error type directly — callers must explicitly check has_value() before accessing the result, making error handling visible rather than an easily-ignored exception that might slip past an untested code path.
std::expected<int, std::string> parseNumber(const std::string& s) {
try {
return std::stoi(s);
} catch (...) {
return std::unexpected("not a valid number");
}
}
auto result = parseNumber("abc");
if (result.has_value()) {
std::cout << *result << "\n";
} else {
std::cout << "Error: " << result.error() << "\n";
}