C++17 introduced std::optional<T>
which lets you augment the values of a type T
with a bonus value known as std::nullopt
which semantically represents the absence of a value. A std::optional
which holds the value std::nullopt
is known as empty.
The basic operations on std::optional
are
- Checking if it holds a value (
has_value()
) - Retrieving the value (
value()
) - Assigning a value (
=
) - Clearing the value and returning to the empty state (
reset()
)
There are other lesser-known powers of the std::optional
.
Contextual conversion
If used in places where the language expects a Boolean (as the controlling expression for if
, while
, for
, ?:
, or on either side of a ||
or &&
), a std::optional
is truthy if is has a value and falsy if it is empty.
if (opt)
is the same as
if (opt.has_value())
Note that this does not test whether the wrapped value is falsy.
std::optional<bool> opt1 = false; if (opt1) { // this executes because the variable // is non-empty (even though it is false) } std::optional<void*> opt2 = nullptr; if (opt2) { // this executes because the variable // is non-empty (even though it is nullptr) }
My opinion: If T
is itself contextually convertible to bool
, write out opt.has_value()
explicitly to avoid confusion.
Equality comparison against a value
An empty std::optional<T>
compares unequal to any T
.
std::optional<int> opt; if (opt == 0) { // does not execute because the variable is empty // and is not equal to any integer. }
My opinion: Use this instead of the more verbose if (opt.has_value() && opt.value() == 0)
.
Ordering comparison against a value
An empty std::optional
compares less than any non-empty std::optional
, and also less than any value.
std::optional<int> opt; if (opt > 0) { // does not execute because "empty" is // less than all values }
My opinion: Avoid except when sorting, because this behavior differs from NaN
(another popular “There’s nothing useful here” value) in that the corresponding opposite-sense test does execute.
if (opt <= 0) { // executes because "empty" is less than all values }
Instead, write it out as
if (opt.has_value() && *opt > 0) // or if (opt.has_value() && *opt < 0)
Note that opt.value()
and *opt
both return the wrapped value but have different failure modes. The explicit opt.value()
call will throw a std::
exception if the object is empty, whereas the *opt
bypasses the verification and you get undefined behavior if the object turns out to be empty after all. In the above case, you can write the code equivalent as
if (opt.has_value() && opt.value() > 0) // or if (opt.has_value() && opt.value() < 0)
because the compiler can optimize out the redundant emptiness test.
The eager defaulting method
value_or
is occasionally useful, too. (I call it “eager” because the default value argument is evaluated before the optional is checked for emptiness.)What
std::optional<T>
lacks is a monadic bindingoperator>>=
and a lazy defaulting methodor_else
which would only evaluate the default value after making sure the optional is empty.Yeah the standard library is missing a whole lot of things to make that feature shine and really useful. It almost seems like someone saw that options from various functional languages were getting really popular (even Java had it for years!) and decided they'd be a good idea in C++ as well (because clearly C++ must have every single feature anybody ever asked for) but didn't really understand the concept.
You very rarely have to explicitly...
Making sense of the latest C++ language perversions is not good for the mental health. If you wanted to make a programming language based on the insane gibbering and whispering of the mad elder gods who roamed the earth before man, it’d look a lot like C++, but probably a bit simpler. Even the gods of the Necronomicon would deem templates a bit risky.
Yeah I was also kind of suprised value_or is not mentioned, as it actually solves some of the problems with the implicit conversion to bool
value_or()
isn’t hidden, though. You hit the “.” and it shows up. But conversion operators and custom comparisons are hidden.The HTML encoding gods have not been kind today.