C++ Logo

std-proposals

Advanced search

Re: P0323 std::expected - void value type?

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Sun, 4 Oct 2020 20:26:30 -0400
On Sun, Oct 4, 2020 at 5:21 PM Emile Cormier via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> Hi Everyone. This is my first time posting to this group, so please be gentle. :-)
>
> I'm in need of a value-or-error mechanism for a C++11 library I'm working on, where I don't want to impose exceptions to the client. Having completed a toy "result<T, E>" implementation inspired by std::expected, I decided to implement a complete C++11-ized version of the P0323R9 proposal.
>
> I am questioning the rationale of allowing void as a value type for std::expected. As I understand it, expected is supposed to be a "vocabulary type" similar to std::optional, or std::variant, yet the latter two don't permit void as one of their contained types (as far as I know).

Functions that don't return a value still need to be able to inform
the caller whether or not an error has taken place. You could just
return the Error type directly, but this requires that the Error type
be able to also signal the *lack* of an error. And while lots and lots
of basic error code types are designed that way, we shouldn't
*require* it at an interface level.

You could return an `optional<Error>`, but `optional` doesn't have the
same machinery or interface as `expected`. If you want to propagate
the Error given to you through your own `expected` return value for
example, you can use `return unexpected(...)`, which is convertible to
any `expected<T, Error>`. You don't have that mechanism in `optional`,
and you probably shouldn't.

It's easier to write template code that can handle errors when you're
using a single return type with a single, well-understood interface.

Now, the question you're really asking is why `void` specifically is
used. Yes, using `void` for this (because of its incompleteness) does
complicate implementations. Fortunately, C++20 concepts can alleviate
a good deal of this complexity (or at least minimize it).

The alternative is to create a type that explicitly means "no value".
But `void` is a natural type to mean "no value". Indeed, it's already
a part of `std::promise/future`; if you want to just signal the
competition of a task (or transmit an exception, you can use
`future<void>`. I imagine that wasn't exactly easy to implement.

The reason this was done for `variant` is that it is *really* hard to
implement a `variant` that takes `void` members. You don't just have
to handle one case; you have to handle each case in each position. It
becomes prohibitively expensive after a while. So using a special type
to mean "no value" makes sense.

And it doesn't make sense for `optional` to allow any kind of "no
value" type. You can't have a NothingOrNothing type ;)

So while it does cause some implementation burden, I'd argue that it's
more in keeping with existing paradigms (particularly with
`promise/future`) to just use `void`.

Received on 2020-10-04 19:26:41