Standard Template Library (STL)
std::optional and std::variant
C++17 sum types — optional values and type-safe tagged unions
Interview: Modern error handling and type safety — shows knowledge of C++17 value semantics
std::optional and std::variant
These C++17 types encode semantic intent in the type system rather than conventions: std::optional<T> explicitly represents "T or nothing", and std::variant<T1, T2...> represents "exactly one of these types".
std::optional
Replaces returning -1, nullptr, or a boolean out-parameter to signal "no value". The caller is forced to check before use. Access: opt.value() (throws if empty), opt.value_or(default), or dereference *opt after checking opt.has_value() or if (opt). No heap allocation — stores the value inline.
std::variant
A type-safe union. Always holds exactly one of its declared types. Unlike C unions, accessing the wrong type throws std::bad_variant_access. Access with std::get<T>(v) or pattern-match with std::visit. Common use: representing AST nodes, command results, or error/success values.
std::any
std::any holds a value of any type (with type erasure) — fully dynamic. Use when the type is truly unknown at compile time. Prefer variant when the possible types are known at compile time — it's type-safe and more efficient.
Interview Corner
Q: Why is std::optional better than returning nullptr or -1?
A: (1) Self-documenting: the function signature explicitly says "may return nothing". (2) Type-safe: can be used with any type, not just pointer types. (3) Forces handling: callers must explicitly check or use value_or(), rather than silently dereferencing a null pointer. (4) No heap allocation: the value is stored inline in the optional object.
Common Pitfalls
- Dereferencing empty optional:
*emptyOptis undefined behavior. Usevalue()(throws) or check has_value() first. - Accessing wrong variant type:
get<T>(v)throws bad_variant_access if v doesn't hold T. Use std::visit for safe exhaustive handling.
Best Practices
- Use optional for functions that might not produce a result — it's cleaner than sentinel values and forces callers to handle the empty case.
- Use std::visit with a visitor that handles all types for exhaustive variant processing — the compiler enforces you handle all alternatives.