C++23 - <expected> Header
Last Updated :
22 Sep, 2023
C++23, the next major version of the C++ programming language, brings several exciting features and enhancements. One of the significant additions in C++23 is the <expected> header, which aims to improve error handling and make code more robust and readable. In this article, we will explore the feature of <expected> header, its purpose, and how it simplifies error handling in C++.
Problem with Error Handling
Error handling in C++ has historically been a complex and error-prone task. C++ has exceptions and error codes, but choosing the right approach and implementing error handling consistently across a codebase can be challenging. Additionally, exceptions can introduce performance overhead in certain scenarios.
<expected> Header in C++
The <expected> header in C++23 introduces a new way to handle errors and expected values. It is inspired by similar constructs in other programming languages, such as Rust's Result and Swift's Result types. The primary goal of <expected> is to provide a more explicit and structured way to handle expected values and errors without relying solely on exceptions or error codes.
The <expected> header introduces two main class templates:
- std::expected
- std::unexpected
Let's dive into each of them.
1. std::expected Class Template
The std::expected is a class template in C++ that serves as a mechanism for managing functions that may return either a valid result or an error. It is a wrapper that is particularly useful in scenarios where exceptions might not be the preferred error-handling approach.
Syntax
The std::expected is used as the value return by the function as shown below:
std::expected<T, E> function_name {
// statements
}
where,
- T: This parameter represents the type of the expected (valid) value that the function may return when it succeeds.
- E: This parameter represents the type of error condition that the function may return when it fails.
When a function returns an instance of std::expected, the caller can easily check whether the result is valid (contains a value of type T) or represents an error (contains an error of type E). This approach provides a clear and structured way to handle errors in a functional, non-exception-based manner.
std::expected Member Functions
Following are some commonly used Member functions of the std::expected class:
S.No.
| Function
| Description
|
---|
1
| value() | It allows you to retrieve the stored value of type T. |
---|
2
| error() | This one facilitates access to the stored error of type E. |
---|
3
| has_value(): | This member function is used to inquire whether the std::expected contains a value or not. It returns true if the std::expected holds a value and false if it holds an error. |
---|
4
| error_code() | When applicable, this member function is employed to convert the stored error into an error code. |
---|
2. std::unexpected Class Template
The std::unexpected is not a standalone class but rather a concept used with std::expected. It helps to define how unexpected errors are handled within a specific context.
When an unexpected error occurs within a function that returns a std::expected, the function can use std::unexpected to specify what action should be taken. This allows developers to define a custom response to unexpected errors.
Syntax
std::expected<T, E> function_name {
// statements
return std::unexpected< E >(some_value);
}
where,
- E: The error type specified in std::expected template.
How to use std::expected?
To use the std::expected class template, follow the given steps:
- Create an instance of std::expected with the expected value type (T) and the error type (E).
- In case of an error, return an instance of std::expected with the error type (E).
- When an operation succeeds, return an instance of std::expected with the expected value (T).
- Utilize methods like has_value() and value() to access the value or error() to obtain the error, as needed.
Examples
Below given is the example code that uses expected in C++:
Example 1: Handling a Successful Division
C++
// C++ program to illustrate the successful execution using
// std::expected feature
#include <expected>
#include <iostream>
using namespace std;
// defining function to return the std::expected object
expected<int, string> divide(int a, int b)
{
if (b == 0) {
// return value in case of exception
return unexpected<string>("Division by zero");
}
// return value when execution is successful
return a / b;
}
// driver code
int main()
{
auto result = divide(10, 2);
// printing result based on the value of std::expected
if (result.has_value()) {
cout << "Result: " << result.value() << endl;
}
else {
cerr << "Error: " << result.error() << endl;
}
return 0;
}
Output
Result: 5
In this example, the divide function returns a std::expected<int, std::string>, representing the result of division or an error message for division by zero.
Example 2: Handling Division by Zero
C++
// C++ program to illustrate the successful execution using
// std::expected feature
#include <expected>
#include <iostream>
using namespace std;
// defining function to return the std::expected object
expected<int, string> divide(int a, int b)
{
if (b == 0) {
// return value in case of exception
return unexpected<string>("Division by zero");
}
// return value when execution is successful
return a / b;
}
// driver code
int main()
{
auto result = divide(10, 0);
// printing result based on the value of std::expected
if (result.has_value()) {
cout << "Result: " << result.value() << endl;
}
else {
cerr << "Error: " << result.error() << endl;
}
return 0;
}
Output
Error: Division by zero
Example 3: Using a Different Error Type
C++
// C++ program to illustrate the behaviour of std::expected
// for different type
#include <expected>
#include <iostream>
using namespace std;
expected<int, float> divideWithFloatError(int a, int b)
{
if (b == 0) {
// Different error type
return unexpected<float>(0.0f);
}
return a / b;
}
// Driver code
int main()
{
auto result3 = divideWithFloatError(6, 0);
if (result3.has_value()) {
cout << "Result 3: " << result3.value() << endl;
}
else {
cerr << "Error 3: " << result3.error() << endl;
}
return 0;
}
Output
Error 3: 0
Advantages of using std::expected
The <expected> header brings forth a multitude of benefits:
- std::expected makes error handling simple and concise within code. It distinctly separates the expected and error scenarios.
- The C++ type system ensures that you cannot inadvertently mix up expected values and errors.
- std::Expected is designed with minimal performance overhead, ensuring efficient error handling.
Conclusion
The <expected> header in C++23 represents a significant leap forward in error handling for C++ developers. With improved code clarity, type safety, and performance characteristics, it offers an efficient solution for handling expected values and errors gracefully.