std::optional
Concepts
Section titled “Concepts”- A value that may be missing
- Custom optional struct (
has_value+value) std::optional<int>- Empty state vs holding a value
std::nullopt,has_value()- A constant for the empty value
- Returning an optional from a function
- Arrays of optionals
- Pointers already hold null (
nullptr) - References cannot be null
Examples to aid understanding
Section titled “Examples to aid understanding”1. Custom optional struct
Section titled “1. Custom optional struct”#include <iostream>
struct OptionalInt{ bool has_value; int value;};
int main(){ OptionalInt a{ true, 5 }; OptionalInt b{ false, 0 };
if (a.has_value) { std::cout << a.value << std::endl; }
if (b.has_value) { std::cout << b.value << std::endl; } else { std::cout << "empty" << std::endl; }}Answer
Sometimes a value may be missing, and this needs to be represented explicitly.
Here, has_value says whether value is meaningful.
aholds a value:has_valueistrue, soa.value(5) is printed.bis empty:has_valueisfalse, sob.value(0) is ignored and"empty"is printed instead.
This pair of a flag plus a value is the whole idea behind an optional.
2. A constant for the empty value
Section titled “2. A constant for the empty value”#include <iostream>
struct OptionalInt{ bool has_value; int value;};
const OptionalInt NO_VALUE{ false, 0 };
int main(){ OptionalInt a{ true, 5 }; OptionalInt b{ NO_VALUE };
if (a.has_value) { std::cout << a.value << std::endl; }
if (b.has_value) { std::cout << b.value << std::endl; } else { std::cout << "empty" << std::endl; }}Answer
Give the empty state a name, so that every empty optional is written the same way.
NO_VALUE holds has_value = false, and the 0 in it is never read —
it is there only because value must contain something.
b is initialized from the constant, so it is empty just like in the previous example.
It prints 5 and "empty".
3. A function returning an optional
Section titled “3. A function returning an optional”#include <iostream>
struct OptionalInt{ bool has_value; int value;};
const OptionalInt NO_VALUE{ false, 0 };
OptionalInt divide(int a, int b){ if (b == 0) { return NO_VALUE; } return OptionalInt{ true, a / b };}
int main(){ OptionalInt a{ divide(7, 2) }; OptionalInt b{ divide(7, 0) };
if (a.has_value) { std::cout << a.value << std::endl; }
if (b.has_value) { std::cout << b.value << std::endl; } else { std::cout << "empty" << std::endl; }}Answer
A function can return an optional to signify a missing result.
Division by zero has no result, so divide returns NO_VALUE in that case.
divide(7, 2)returns an optional holding3;divide(7, 0)returns the empty constant, so"empty"is printed instead.
It prints 3 and "empty".
4. An array of optionals
Section titled “4. An array of optionals”#include <array>#include <iostream>
struct OptionalInt{ bool has_value; int value;};
const OptionalInt NO_VALUE{ false, 0 };
int main(){ std::array<OptionalInt, 3> arr{ { true, 1 }, NO_VALUE, { true, 3 }, };
for (OptionalInt el : arr) { if (el.has_value) { std::cout << el.value << std::endl; } else { std::cout << "empty" << std::endl; } }}Answer
Each element is checked in turn: if it holds a value, the value is printed,
otherwise "empty" is printed.
It prints 1, "empty", 3.
5. std::optional<int>
Section titled “5. std::optional<int>”#include <iostream>#include <optional>
int main(){ std::optional<int> a{ 5 }; std::optional<int> b{ std::nullopt };
if (a.has_value()) { std::cout << a.value() << std::endl; }
if (b.has_value()) { std::cout << b.value() << std::endl; } else { std::cout << "empty" << std::endl; }}Answer
std::optional<int> is the standard version of the custom struct from the earlier examples.
It either holds an int or holds nothing.
aholds a value:std::optional<int>{ 5 }stores5, sohas_value()istrueandvalue()gives5.bis empty:std::nulloptmeans “no value”, sohas_value()isfalse,value()must not be used, and"empty"is printed instead.
It prints 5 and "empty".
6. A function returning an optional (std::optional)
Section titled “6. A function returning an optional (std::optional)”#include <iostream>#include <optional>
std::optional<int> divide(int a, int b){ if (b == 0) { return std::nullopt; } return a / b;}
int main(){ std::optional<int> a{ divide(7, 2) }; std::optional<int> b{ divide(7, 0) };
if (a.has_value()) { std::cout << a.value() << std::endl; }
if (b.has_value()) { std::cout << b.value() << std::endl; } else { std::cout << "empty" << std::endl; }}Answer
This is the same divide as in the custom example, but with the standard type.
Division by zero returns std::nullopt.
Note that return a / b; needs no braces: a plain int becomes
an std::optional<int> holding that value automatically.
It prints 3 and "empty".
7. An array of optionals (std::optional)
Section titled “7. An array of optionals (std::optional)”#include <array>#include <iostream>#include <optional>
int main(){ std::array<std::optional<int>, 3> arr{ 1, std::nullopt, 3, };
for (std::optional<int> x : arr) { if (x.has_value()) { std::cout << x.value() << std::endl; } else { std::cout << "empty" << std::endl; } }}Answer
This is the same loop as in the custom example, but with the standard type.
A plain 1 becomes an std::optional<int> holding 1 automatically.
It prints 1, "empty", 3.
8. A pointer can already hold null
Section titled “8. A pointer can already hold null”#include <iostream>
int main(){ int a{ 5 }; int* p{ &a }; int* q{ nullptr };
if (p != nullptr) { std::cout << *p << std::endl; }
if (q != nullptr) { std::cout << *q << std::endl; } else { std::cout << "empty" << std::endl; }}Answer
Pointers already have a built-in empty state — nullptr.
They need no extra flag like has_value.
ppoints ata, so the check passes and5is printed;qholdsnullptr, so the check fails and"empty"is printed instead.
It prints 5 and "empty".
9. A reference cannot hold null
Section titled “9. A reference cannot hold null”int main(){ int& x{ nullptr };}Answer
This will not compile: a reference must be bound to a real variable of type int,
and nullptr is not one. References have no empty state.
10. Taking the address of nullptr
Section titled “10. Taking the address of nullptr”int main(){ int& x{ &nullptr };}Answer
This will not compile either: & can only be applied to a variable,
and nullptr is not a variable, so its address cannot be taken.
11. A reference bound to null is UB
Section titled “11. A reference bound to null is UB”#include <iostream>
int main(){ int* a = nullptr; int& x{ *a }; std::cout << x << std::endl;}Answer
This compiles, but it is UB: *a follows a null address.
Creating the reference already dereferences null,
so anything can happen (usually a crash).
Unlike an optional, there is nothing to check — the error is not represented in any way.
Note that writing &a here would not compile at all:
&a is an int**, not an int.
12. std::optional for pointers is pointless
Section titled “12. std::optional for pointers is pointless”std::optional for pointers is pointless: pointers are allowed to hold null already,
and there is no way to express the opposite — a pointer guaranteed not to be null.
A reference should be used when wanting to pass a pointer guaranteed not to be null.
#include <iostream>
void print(int* p){ if (p != nullptr) { std::cout << *p << std::endl; }}
void printRef(int& r){ std::cout << r << std::endl;}
int main(){ int a{ 5 }; print(&a); print(nullptr); printRef(a);}Answer
Wrapping a pointer as std::optional<int*> adds nothing:
the pointer already expresses “maybe null” through nullptr.
And an optional cannot express “guaranteed not null” either.
That guarantee is what a reference gives: printRef needs no check,
because the caller is forced to pass a real variable.
It prints 5 twice: once from print(&a), once from printRef(a).
print(nullptr) prints nothing.