If you've ever written a command-line tool in C++, you've hit this moment: a file won't open, a flag is malformed, a config value is garbage — and now what? Do you throw ? Do you return some sentinel value and hope everyone remembers to check it? C++23 gave us a new option, std::expected , and it's changed how a lot of people answer that question. This article walks through both approaches, explains the tradeoffs in plain English, and shows real code so you can decide for yourself. The Core Idea, In One Sentence Exceptions say "something went wrong, stop everything, and let some code way up the call stack deal with it." std::expected says "this function might fail, so its return type honestly says so — you can't ignore it without looking silly." That's really the whole philosophical difference. Everything else is details. A Mental Model: The Post Office vs. The Vending Machine Think of exceptions like mailing a letter. You drop it in...
The Problem We're Actually Solving Let's start before the code. Imagine you write a function that divides two numbers. Easy, right? Except... what happens if someone tries to divide by zero? Your function needs a way to say "hey, something went wrong here" without crashing the whole program or lying about the result. For decades, C++ programmers have had a few messy options: Throw an exception — works, but exceptions are expensive, and some codebases (game engines, embedded systems) avoid them entirely. Return an error code — cheap, but easy to ignore. Nobody checks return codes reliably, and you lose the "why" behind the failure. Use an output parameter — pass a pointer or reference to fill in with an error. Clunky and easy to mess up. Return a std::optional — tells you something failed, but not what or why . C++23 gives us a much cleaner tool: std::expected . Think of it as a box that either contains your successful result, or contains the ...