Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why are there no monadic operations in std::expected<void, Error>

I modeled a function that returns an instance of an std::expected<void, Error> – I told myself "I can use a new standard so I will design my library accordingly" – and I was very positive to have nice and concrete error handling. Now it turns out, that all the monadic operations on the std::expected only work on non-void items. Even though there is a specialization for void, the monadic operations are not available.
I understand that or_else needs to return the value – but there is a specialization for void, so why should this not work?

std::expected<void, Error> fun (int);

fun (19).and_then ([]() { doSomething(); }).or_else ([] (Error e) { std::println ("Uh oh error.."); });

This yields:

/usr/include/c++/14.2.1/expected:1586:37: error: static assertion failed
 1586 |           static_assert(__expected::__is_expected<_Up>);
      |                         ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
/usr/include/c++/14.2.1/expected:1586:37: note: ‘std::__expected::__is_expected<void>’ evaluates to false
/usr/include/c++/14.2.1/expected:1587:25: error: ‘std::remove_cvref<void>::type’ {aka ‘void’} is not a class, struct, or union type
 1587 |           static_assert(is_same_v<typename _Up::error_type, _Er>);
      |                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/14.2.1/expected:1592:20: error: expression list treated as compound expression in functional cast [-fpermissive]
 1592 |             return _Up(unexpect, std::move(_M_unex));
      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/david/tests/cpp/src/expect.cpp: In function ‘int main()’:
/home/david/tests/cpp/src/expect.cpp:49:26: error: invalid use of ‘void’

What’s the problem here? Is this feature only half done (so the standard is incomplete) or am I misunderstanding its point?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

The problem is that the callable to and_then and or_else needs to return a specialization of std::expected for chaining to work. Your lambdas do not.

If fun were to return an unexpected (an error), and_then won’t call your callable, but it will need to forward the error into the same sort of unexpected type that the lambda returns. So the lambda for and_then should return std::unexpected<..., Error>. You can choose what ever "expected" type you want the lambda to return, just as long as it’s the same Error type. You can stick to void of course.

fun (19).and_then ([]() { doSomething(); return std::expected<void, Error>(); })

Conversly, the callbale to or_else can choose a different error type, but needs to preserve the expected type in its returned std::expected specialization. But all in all, the same treatment should work.

fun (19).and_then ([]() { 
          doSomething();
          return std::expected<void, Error>();
}).or_else ([] (Error e) { 
          std::println ("Uh oh error..");
          return std::expected<void, Error>();
});

Note I opted to have or_else swallow the error and return an std::expected with a value. You could choose to forward the error, of course.

...
.or_else ([] (Error e) { 
          std::println ("Uh oh error..");
          return std::expected<void, Error>(std::unexpect, e);
});
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading