c++error-handlingc++23std-expected

How can we forward a failed std::expected up through the call stack


I have a std::expected use case like this:

class MyClass {
public:
    enum class error {
        e1 // ...
    };

    using index_return_type = std::expected<unsigned, error>;

    index_return_type add_data(data d);
    index_return_type update_data(data d);

    private:

    std::expected<void, error> expand_to_fit(data) {
        // Imagine i fail here
        return std::unexpected{error::e1};
    }
};

In add_data and update_data i would always immediately call expand_to_fit which can fail. If it does, essentially the error is already there, so i simply want to push it forward to outside. The best i have come up with is somehting like:

index_return_type add_data(data d) {
    if (auto it_fit = expand_to_fit(d); !it_fit) {
        return std::unexpected{it_fit.error()};
    }
    // The rest of the function
}

Live example.

But i don't love it.

  1. The naming of it_fit is arbitrary, what if it could fit, but the expand_to_fit function failed for other reasons? It makes this code hard to read (and a little deceptive).
  2. The ergonomics of this little check seem a bit rough to have to write all over the code and it makes the code harder to read imo.

Is there a better way to forward a failed std::expected up through your calling functions?

To extend this, imagine that i have a series of short checks. If i was using exceptions i might have something like:

void expand_to_fit(data) {

    if (!connected_to_database()) {
       throw std::runtime_error("Lost connection");
    }
    
    if (has_data(data)) {
        throw std::runtime_error("Duplicate");
    }
    
    if (!is_input_in_max_range(data)) {
        throw std::runtime_error("Cannot fit");
    }
   
    // ... Rest of function
}

How might we handle this with expected?


Solution

  • The idiomatic way would be to use one of the monadic operations:

    Since the expected type returned from expand_to_fit is void and the std::expected will contain the error if it occured inside expand_to_fit, you could use transform to return the expected unsigned if expand_to_fit does not contain error:

    index_return_type add_data(data d) {
        return expand_to_fit(d).transform([&]{
            // the rest of the function wrapped inside a lambda
            return 0u; // return the expected unsigned
        });
    }