C++ std::optional Using optionals to represent the failure of a function


Example

Before C++17, a function typically represented failure in one of several ways:

  • A null pointer was returned.
    • e.g. Calling a function Delegate *App::get_delegate() on an App instance that did not have a delegate would return nullptr.
    • This is a good solution for objects that have been dynamically allocated or are large and managed by pointers, but isn't a good solution for small objects that are typically stack-allocated and passed by copying.
  • A specific value of the return type was reserved to indicate failure.
    • e.g. Calling a function unsigned shortest_path_distance(Vertex a, Vertex b) on two vertices that are not connected may return zero to indicate this fact.
  • The value was paired together with a bool to indicate is the returned value was meaningful.
    • e.g. Calling a function std::pair<int, bool> parse(const std::string &str) with a string argument that is not an integer would return a pair with an undefined int and a bool set to false.

In this example, John is given two pets, Fluffy and Furball. The function Person::pet_with_name() is then called to retrieve John's pet Whiskers. Since John does not have a pet named Whiskers, the function fails and std::nullopt is returned instead.

#include <iostream>
#include <optional>
#include <string>
#include <vector>

struct Animal {
    std::string name;
};

struct Person {
    std::string name;
    std::vector<Animal> pets;
    
    std::optional<Animal> pet_with_name(const std::string &name) {
        for (const Animal &pet : pets) {
            if (pet.name == name) {
                return pet;
            }
        }
        return std::nullopt;
    }
};

int main() {
    Person john;
    john.name = "John";
    
    Animal fluffy;
    fluffy.name = "Fluffy";
    john.pets.push_back(fluffy);
    
    Animal furball;
    furball.name = "Furball";
    john.pets.push_back(furball);
    
    std::optional<Animal> whiskers = john.pet_with_name("Whiskers");
    if (whiskers) {
        std::cout << "John has a pet named Whiskers." << std::endl;
    }
    else {
        std::cout << "Whiskers must not belong to John." << std::endl;
    }
}