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
}
But i don't love it.
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).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?
The idiomatic way would be to use one of the monadic operations:
and_then
- returns the result of the given function on the expected value if it exists; otherwise, returns the expected itself.
transform
- returns an expected containing the transformed expected value if it exists; otherwise, returns the expected itself
or_else
- returns the expected itself if it contains an expected value; otherwise, returns the result of the given function on the unexpected value
transform_error
- returns the expected itself if it contains an expected value; otherwise, returns an expected containing the transformed unexpected value
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
});
}