How would you use Alexandrescu's Expected<T> with void functions?
c++, c++11, error-handling, runtime-error
Solution
Have any of you tried Expected; in practice?
It's quite natural, I used it even before I saw this talk.
How would you apply this idiom to functions returning nothing (that is, void functions)?
The form presented in the slides has some subtle implications:
- The exception is bound to the value.
- It's ok to handle the exception as you wish.
- If the value ignored for some reasons, the exception is suppressed.
This does not hold if you have `expected<void>`, because since nobody is interested in the `void` value the exception is always ignored. I would force this as I would force reading from `expected<T>` in Alexandrescus class, with assertions and an explicit `suppress` member function. Rethrowing the exception from the destructor is not allowed for good reasons, so it has to be done with assertions.
template <typename T> struct expected;
#ifdef NDEBUG // no asserts
template <> class expected<void> {
std::exception_ptr spam;
public:
template <typename E>
expected(E const& e) : spam(std::make_exception_ptr(e)) {}
expected(expected&& o) : spam(std::move(o.spam)) {}
expected() : spam() {}
bool valid() const { return !spam; }
void get() const { if (!valid()) std::rethrow_exception(spam); }
void suppress() {}
};
#else // with asserts, check if return value is checked
// if all assertions do succeed, the other code is also correct
// note: do NOT write "assert(expected.valid());"
template <> class expected<void> {
std::exception_ptr spam;
mutable std::atomic_bool read; // threadsafe
public:
template <typename E>
expected(E const& e) : spam(std::make_exception_ptr(e)), read(false) {}
expected(expected&& o) : spam(std::move(o.spam)), read(o.read.load()) {}
expected() : spam(), read(false) {}
bool valid() const { read=true; return !spam; }
void get() const { if (!valid()) std::rethrow_exception(spam); }
void suppress() { read=true; }
~expected() { assert(read); }
};
#endif
expected<void> calculate(int i)
{
if (!i) return std::invalid_argument("i must be non-null");
return {};
}
int main()
{
calculate(0).suppress(); // suppressing must be explicit
if (!calculate(1).valid())
return 1;
calculate(5); // assert fails
}
Problem
So I ran across this (IMHO) very nice idea of using a composite structure of a return value and an exception - `Expected<T>`. It overcomes many shortcomings of the traditional methods of error handling (exceptions, error codes). See the Andrei Alexandrescu's talk (Systematic Error Handling in C++) and its slides. The exceptions and error codes have basically the same usage scenarios with functions that return something and the ones that don't. `Expected<T>`, on the other hand, seems to be targeted only at functions that return values. So, my questions are: - Have any of you tried `Expected<T>` in practice? - How would you apply this idiom to functions returning nothing (that is, void functions)? Update: I guess I should clarify my question. The `Expected<void>` specialization makes sense, but I'm more interested in how it would be used - the consistent usage idiom. The implementation itself is secondary (and easy). For example, Alexandrescu gives this example (a bit edited): ``` string s = readline(); auto x = parseInt(s).get(); // throw on error auto y = parseInt(s); // won’t throw if (!y.valid()) { // ... } ``` This code is "clean" in a way that it just flows naturally. We need the value - we get it. However, with `expected<void>` one would have to capture the returned variable and perform some operation on it (like `.throwIfError()` or something), which is not as elegant. And obviously, `.get()` doesn't make sense with void. So, what would your code look like if you had another function, say `toUpper(s)`, which modifies the string in-place and has no return value?