Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would you use Alexandrescu's Expected<T> with void functions?

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?

like image 498
Alex Avatar asked Feb 17 '13 16:02

Alex


People also ask

How do you use the void function?

Void functions are created and used just like value-returning functions except they do not return a value after the function executes. In lieu of a data type, void functions use the keyword "void." A void function performs a task, and then control returns back to the caller--but, it does not return a value.

Why would you use a void function?

In computer programming, when void is used as a function return type, it indicates that the function does not return a value. When void appears in a pointer declaration, it specifies that the pointer is universal.

What do you return for a void function?

A void function cannot return any values. But we can use the return statement. It indicates that the function is terminated. It increases the readability of code.

What is std :: expected?

In contrast, std::expected can indicate an expected value and an error value, which is equivalent to the two-member std::variant , but is more convenient to use on the interface. Think of it as a new kind of error handling.


Video Answer


2 Answers

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 } 
like image 53
ipc Avatar answered Sep 25 '22 04:09

ipc


Even though it might appear new for someone focused solely on C-ish languages, to those of us who had a taste of languages supporting sum-types, it's not.

For example, in Haskell you have:

data Maybe a = Nothing | Just a  data Either a b = Left a | Right b 

Where the | reads or and the first element (Nothing, Just, Left, Right) is just a "tag". Essentially sum-types are just discriminating unions.

Here, you would have Expected<T> be something like: Either T Exception with a specialization for Expected<void> which is akin to Maybe Exception.

like image 26
Matthieu M. Avatar answered Sep 25 '22 04:09

Matthieu M.