A function that is supposed to return a value may not have one available to give back. Returning a special "not found" value of the same type is only appropriate when it is guaranteed that no bit pattern in the appropriate type set will be a valid answer. std::optional removes that assumption by making the fact of value existence or absence part of the type itself.
A first look at the type shows how the value will be stored and accessed:
#include <iostream>
#include <optional>
int main()
{
// Represent a value that may be absent
std::optional<int> o{42};
std::cout << o.value() << '\n';
return 0;
}
The object o is an std::optional<int>, and the braced initializer places the value 42 inside the contained state. The member value takes care of returning the integer contained within; here it is printed. This object o is engaged, i.e., it contains a value.
An optional may also be empty:
#include <iostream>
#include <optional>
int main()
{
std::optional<int> o{std::nullopt};
// Check this condition before entering the block
if (o.has_value())
std::cout << o.value() << '\n';
else
std::cout << "empty\n";
return 0;
}
The constructor accepting std::nullopt makes the optional disengaged. The member has_value returns false, and control falls into the else branch where the word "empty" is printed. Calling value on a disengaged optional throws an exception, which this test prevents.
This same check can be written concisely using the conversion to bool:
#include <iostream>
#include <optional>
int main()
{
std::optional<int> o{std::nullopt};
if (o)
std::cout << o.value() << '\n';
else
std::cout << "empty\n";
return 0;
}
An optional in a conditional will evaluate to true if it is engaged and false if it is empty. The behavior is the same as the last example; only the syntax of the test has changed.
A function that may have results to return or may not is a natural candidate for returning an optional value. Consider a simple lookup with only a few known keys:
#include <iostream>
#include <optional>
std::optional<int> lookup(int key)
{
if (key == 1)
return 100;
if (key == 2)
return 200;
return std::nullopt;
}
int main()
{
std::optional<int> result = lookup(1);
if (result)
std::cout << result.value() << '\n';
else
std::cout << "not found\n";
return 0;
}
The lookup function is declared as returning an std::optional<int>. In the cases where the key is recognized, an engaged optional containing the value is returned. In other cases, std::nullopt is returned.
In main, the optional value is first assigned to result and then tested.
Because the key 1 is known, 100 is printed.
By changing the argument:
#include <iostream>
#include <optional>
std::optional<int> lookup(int key)
{
if (key == 1)
return 100;
if (key == 2)
return 200;
return std::nullopt;
}
int main()
{
std::optional<int> result = lookup(99);
if (result)
std::cout << result.value() << '\n';
else
std::cout << "not found\n";
return 0;
}
lookup will return an empty optional. Now the test will fail, and the "not found" message will be printed instead. Notice that no special sentinel value was required; emptiness is contained within the optional itself.
When a default is appropriate, the value_or member provides a clean alternative without explicit checking:
#include <iostream>
#include <optional>
std::optional<int> lookup(int key)
{
if (key == 1)
return 100;
return std::nullopt;
}
int main()
{
std::cout << lookup(1).value_or(0) << '\n';
std::cout << lookup(99).value_or(0) << '\n';
return 0;
}
The first call returns an engaged optional, and value_or provides the stored value 100. The second call returns an empty optional, and value_or provides the alternative 0. In both cases, a single expression is used to acquire an int which is guaranteed to be safe to print.
std::optional takes care to separate the "is there a value?" question from the question of what that value is. When there may be no answer, an optional value can be returned so that the overload set isn't extended with sentinel values. The user of the optional has no choice but to check whether there is a value (or use value_or) to know if the contained value is valid.
0 comments
No comments yet. Be the first.