Step 3 of 7
optional for "maybe"
When the only possible failure is "there's no result", std::optional says it right in the return type: the function returns either a value or nothing. There's no separate out-parameter, and the empty case is part of the type, so callers see it and check it (reading an empty optional with * is still a bug).
#include <iostream>
#include <map>
#include <optional>
#include <string>
std::optional<std::string> phone_of(const std::map<std::string, std::string>& book, const std::string& name) {
auto it = book.find(name);
if (it == book.end()) return std::nullopt;
return it->second;
}
int main() {
std::map<std::string, std::string> book = {{"ada", "555-0101"}, {"alan", "555-0199"}};
for (const char* who : {"alan", "grace"}) {
auto p = phone_of(book, who);
std::cout << who << ": " << p.value_or("unknown") << "\n";
}
}
alan: 555-0199
grace: unknown
Compared with a status code
| Question | status code | std::optional |
|---|---|---|
| Result delivered | out-parameter | return value |
| Caller forgets to check | wrong data used silently | the missing case is spelled out in the type, so it's hard to forget |
| Says why it failed | no | no |
Why not return -1?
For an index, -1 is the classic "not found" value, but the return type std::size_t is unsigned, so -1 would become a huge number that looks like a valid index. std::optional<std::size_t> avoids the whole problem.
For your search: loop over the indexes, return i on a match, and return std::nullopt after the loop.
Your turn: write std::optional<std::size_t> find_player(const std::vector<std::string>& team, const std::string& name) returning the index or std::nullopt.