C++ std::optional
std::optional<T> represents either a value of type T or no value. It is useful when absence is a normal result, not an error.
Basic example
The function returns 42 when a value is available and std::nullopt when it is not. The if checks whether the optional contains a value; *answer reads that value.
Include <optional> and compile as C++17 or newer:
How to use it
Create an optional with a value or without one:
Use if, has_value(), or value_or() depending on what the program needs:
| Operation | Meaning |
|---|---|
if (value) |
Check whether a value is present. |
*value |
Read the contained value after checking. |
value.value() |
Read the value, or throw std::bad_optional_access if empty. |
value.value_or(default_value) |
Read the value, or use a fallback. |
value.reset() |
Remove the contained value. |
Check before dereferencing
Dereferencing an empty optional with *value is undefined behavior. Check it first, or use value_or() when a default is appropriate.
Why not use a sentinel value?
A function returning -1 for “missing” cannot also treat -1 as valid data. An optional stores presence separately, so every int, including 0 and negative values, remains available to the program.
The condition is true because the optional contains a value. It does not test whether that value is nonzero.
Hands-on: optional retry count
The complete optional_demo.cpp reads an optional retry count from the command line. With no argument, it uses a default of 3; with an argument, it parses and uses that value.
Expected output:
Now change the program:
- Change the default retry count from
3to2. - Reject configured values greater than
10. - Print a clear error and return a nonzero exit status for rejected input.
- Confirm that
0,2, and no argument still work.
Quiz
1. Is zero present?
What does this print?
2. What does value_or() return?
3. Why is std::optional<int> better than returning -1 for a missing integer?
Advanced topics
Must know: lifetime, emplace(), and reset()
An optional contains its value directly. emplace() constructs a value inside it, replacing any current value, while reset() destroys the value and makes the optional empty.
Use emplace() when constructing the value in place is clearer or avoids a temporary. Normal assignment is usually simpler for basic values.
Must know: copy and move behavior
Copying an optional copies its contained value. Moving it moves the contained value, but does not guarantee that the source optional becomes empty.
After the move, destination contains "camera". source may still report that it has a value, but that string is in a valid, unspecified moved-from state. Do not use has_value() to test whether an object was moved from.
An optional can also contain a move-only type. Use that only when “no object” and the contained type's own empty state have different meanings.
Must know: C++23 optional pipelines
C++23 adds three operations for processing a value without repeatedly writing if checks:
| Operation | Runs when | Callable returns |
|---|---|---|
transform() |
A value is present | A plain value, wrapped in an optional automatically |
and_then() |
A value is present | Another std::optional |
or_else() |
The optional is empty | A replacement std::optional |
Compile this style with C++23:
Keep a direct if when it is easier to read. Pipelines are most useful when several optional-producing steps must be chained.
Must know: choosing the right return type
| Situation | Prefer |
|---|---|
| A value may normally be absent | std::optional<T> |
| Failure needs an explanation | std::expected<T, E> in C++23 |
| Failure is exceptional and cannot be handled locally | An exception |
| Shared or transferred ownership is required | A smart pointer |
| A function parameter has a common default | A default argument or overload |
An optional parameter can be appropriate when the caller must explicitly distinguish “not supplied” from every possible value. Do not use it merely to avoid writing an overload.
Common traps
std::optional<bool>has three states: empty,false, andtrue. Test presence separately from the contained Boolean.std::optional<T&>is not allowed. Usestd::reference_wrapper<T>when a non-owning optional reference is genuinely needed.- Nested optionals represent more than two states, but are usually harder to understand; use them only when each level has a distinct meaning.
value_or()eagerly evaluates its fallback argument, even when the optional contains a value. Avoid putting expensive work directly in that argument.