Open In App

C++23 – <expected> Header

Last Updated : 22 Sep, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

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:

  1. std::expected
  2. 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:

  1. Create an instance of std::expected with the expected value type (T) and the error type (E).
  2. In case of an error, return an instance of std::expected with the error type (E).
  3. When an operation succeeds, return an instance of std::expected with the expected value (T).
  4. 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:

  1. std::expected makes error handling simple and concise within code. It distinctly separates the expected and error scenarios.
  2. The C++ type system ensures that you cannot inadvertently mix up expected values and errors.
  3. 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.
 



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads