You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In this example, `safe_sqrt` returns an `std::expected<double, std::string>`. If the input is valid, it returns the square root; otherwise, it returns an error message. The caller can then check if the result is valid and handle the error accordingly. So how does this compare to traditional error handling methods?
43
+
44
+
### Comparison to Traditional Error Handling
45
+
46
+
Before `std::expected`, there were typically two main approaches to error handling in C++: exceptions and error codes. While exceptions can be powerful, they typically bring with them more complexity in control flow and then there is the discussion which errors should cause an exception to be thrown and which should not. The benefit of exceptions is that they allow for clean separation of error handling code and for propagation of errors up the call stack.
47
+
Error codes on the other hand tend to either clutter the code by requiring out-parameters or have the problem of being either ignored or misunderstood by the caller. While [nodiscard](https://en.cppreference.com/w/cpp/language/attributes/nodiscard) can help with ignored return values, it still does not solve the problem that the caller has to semantically understand the meaning of the return value.
48
+
49
+
`std::expected` provides a middle ground. It makes error handling explicit in the type system, allowing to pass semantic information about the error back to the caller. The beauty of `std::expected`is also, that it can help to discern between expected or recoverable errors (e.g. file not found, invalid input) and unexpected or unrecoverable errors (e.g. out of memory, logic errors) which should still be handled via exceptions.
50
+
51
+
> **Tip:** Use `std::expected` for recoverable errors where the caller can take action based on the error, and reserve exceptions for truly exceptional situations.
52
+
53
+
Let's look at a more complex example that demonstrates how `std::expected` can be used in a real-world scenario.
54
+
55
+
### Real world example: Reading a QR code from an image
56
+
57
+
Let's suppose we want to write a function that reads a QR code from binary image data. The function generally has three paths:
58
+
59
+
1. The image contains a valid QR code and we can return the decoded string.
60
+
2. The image does not contain a QR code and we want to return an error indicating that.
61
+
3. The image data is unreadable (e.g. corrupted or unrecognizable format) and we want to throw an exception.
31
62
32
-
To use this function, you can check if the result is valid and handle the error accordingly:
63
+
While the first two paths are expected and recoverable errors, the third path is an unexpected error that should be handled via exceptions. So the implementation could look like this:
// Assume parse_image_data is a function that parses the image data and returns the QR code string or throws on failure
78
+
std::string parsed_data = parse_image_data(image_data); // May throw exceptions on failure
79
+
80
+
if (parsed_data.empty()) {
81
+
return std::unexpected("No QR code found");
82
+
}
83
+
84
+
return parsed_data;
85
+
}
86
+
```
87
+
88
+
Note that in this example, both the success and error type are strings, but they could be any type. In a lot of cases, it might still make sense to use an enum or a custom error type for the error case to make it more structured. However, by using `std::expected`, we already are able to add a lot more context to the function without cluttering the code. This already is a big improvement over returning, but there is more.
89
+
90
+
### Monadic chaining with `and_then`
91
+
36
92
37
-
int main() {
38
-
auto result = safe_sqrt(-1);
39
-
if (result) {
40
-
std::cout << "Square root: " << *result << '\n';
93
+
Monadic chaining lets you compose a sequence of operations that may fail, without deeply nested `if`/`else`. With `std::expected`, you chain:
94
+
-`and_then` when the next step itself may fail and returns another `expected`.
95
+
-`transform` when the next step cannot fail and just maps the value.
96
+
-`or_else` to act on or recover from an error.
97
+
98
+
Below is a continuation of the QR example, showing a pipeline that:
0 commit comments