Practical Error Handling in C++ with std::optional and std::variant
Why Traditional Error Codes Fall Short
Most of us have lived through the era of returning int error codes or using output parameters like bool parse(Config&, std::string& err). The problem isn’t that they don’t work — they do — it’s that they force the caller to remember a convention, and the compiler can’t help you when you forget to check the result. I’ve seen production bugs caused by a missed if (!rc) because the function looked like it returned a value.
Modern C++ gives us types that make the contract explicit in the signature, so the compiler becomes your first line of defense.
Enter std::optional
std::optional (C++17) models a value that may be absent. It’s perfect for functions that either produce a result or have nothing meaningful to return — think lookup tables, cache reads, or parsing a single integer.
// Returns the parsed port if the string is a valid number in range.
std::optional<uint16_t> parse_port(const std::string& s) {
if (s.empty()) return std::nullopt;
try {
long v = std::stol(s);
if (v < 0 || v > 65535) return std::nullopt;
return static_cast<uint16_t>(v);
} catch (...) {
return std::nullopt;
}
}
The caller writes:
if (auto port = parse_port(cfg["port\