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?

Original source