Functional exception-less error handling with C++23’s optional and expected
This post is an updated version of one I made over five years ago, now that everything I talked about is in the standard and implemented in Visual Studio. In software things can go wrong. Sometimes we might expect them to go wrong. Sometimes it’s a surprise. In most cases we want to build in some way of handling these misfortunes. Let’s call them disappointments. std::optional was added in C++17 to provide a new standard way of expressing disappointments and more, and it has been extended in C++23 with a new interface inspired by functional programming. std::optional expresses “either a T or nothing”. C++23 comes with a new type, std::expected which expresses “either the expected T, or some E telling you what went wrong”. This type also comes with that special new functional interface. As of Visual Studio 2022 version 17.6 Preview 3, all of these features are available in our standard library. Armed with an STL implementation you can try yourself, I’m going to exhibit how to use std::optional‘s new interface, and the new std::expected to handle disappointments. One way to express and handle disappointments is exceptions: void pet_cat() { try { auto cat = find_cat(); scratch_behind_ears(cat); } catch (const no_cat_found& err) { //oh no be_sad(); } } There are a myriad of discussions, resources, rants, tirades, and debates about the value of exceptions123456, and I will not repeat them here. Suffice to say that there are cases in which exceptions are not the best tool for the job. For the sake of being uncontroversial, I’ll take the example of disappointments which are expected within reasonable use of an API. The Internet loves cats. Suppose that you and I are involved in the business of producing the cutest images of cats the world has ever seen. We have produced a high-quality C++ library geared towards this sole aim, and we want it to be at the bleeding edge of modern C++. A common operation in feline cutification programs is to locate cats in a given image. How should we express this in our API? One option is exceptions: // Throws no_cat_found if a cat is not found. image_view find_cat (image_view img); This function takes a view of an image and returns a smaller view which contains the first cat it finds. If it does not find a cat, then it throws an exception. If we’re going to be giving this function a million images, half of which do not contain cats, then that’s a lot of exceptions being thrown. In fact, we’re pretty much using exceptions for control flow at that point, which is A Bad Thing™. What we really want to express is a function which either returns a cat if it finds one, or it returns nothing. Enter std::optional. std::optional find_cat (image_view img); std::optional was introduced in C++17 for representing a value which may or may not be present. It is intended to be a vocabulary type — i.e. the canonical choice for expressing some concept in your code. The difference between this signature and the previous one is powerful; we’ve moved the description of what happens on an error from the documentation into the type system. Now it’s impossible for the user to forget to read the docs, because the compiler is reading them for us, […]
