Vocabulary Types: optional, variant and tuple
Type-safe tools for maybe-a-value, one-of-several-types, and grouping
union.
- use
std::optional<T>to model a value that may or may not be present, - use
std::variant<A, B, ...>as a type-safe union that always knows which type it holds, - group a few values with
std::pairandstd::tuple, - unpack any of them cleanly with structured bindings.
This topic assumes you are comfortable with templates, references and move semantics from earlier in this course. All examples compile with a C++17 compiler.
1. std::optional - maybe a value
A function often needs to say "here is a result" or "there is no result". The old ways of expressing that are all fragile: return a magic sentinel like -1 (what if -1 is a valid answer?), return a bool and write the real answer through an output parameter, or return a raw pointer that might be nullptr (who owns it?). std::optional<T> (from <optional>) makes "absent" a first-class, type-checked state.
An optional<T> either contains a T or is empty. You test it like a bool, read the value with * or -> (only when it is engaged), and supply a fallback with value_or. The empty state is written std::nullopt.
#include <optional>
#include <string>
using namespace std;
optional<int> parsePort(const string& scheme) {
if (scheme == "http") return 80;
if (scheme == "https") return 443;
return nullopt; // no sensible value to return
}
The caller cannot forget to check, because the value lives inside the optional and you must open it first:
auto p = parsePort("https");
if (p) { // is it engaged?
cout << "port = " << *p << "\n"; // safe: we checked
}
int port = parsePort("ftp").value_or(-1); // fallback if empty
* or -> is undefined behaviour, exactly like dereferencing a null pointer. Always check first, or use value_or. If you prefer an exception instead, call .value(), which throws std::bad_optional_access when empty.