On Sun, Oct 4, 2020 at 5:21 PM Emile Cormier via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
Hi Everyone. This is my first time posting to this group, so please be gentle. :-) [...]
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).

Right, but std::future/std::promise do.
The idea behind future<void> is that we want to be able to write
    auto async(auto f) -> std::future<decltype(f())>;
without worrying about some special case when f() is void, or when f() returns a reference type. We want it to just work with any return-able type.

Similarly, expected<T,E> is at least arguably supposed to be used as a return type:
    auto probably_but_not_certainly(auto f) -> std::expected<decltype(f()), E>;
So it makes sense for it to support all return-able types.

"But what about optional<T>?" you say, rightfully.
    auto maybe(auto f) -> std::optional<decltype(f)>;  // not generic
You can't use this `maybe` on callables that return void, nor on callables that return references. Why not? Well, definitely an artifact of the standardization process. I would say that std::optional was trying to serve two masters. On the one hand it's trying to be a generic utility type, but it's also looking up to its big brother std::tuple, which itself was serving two masters — std::make_tuple/std::forward_as_tuple on the one hand and std::tie on the other. So it became unclear whether std::optional<U&> should have copy semantics like std::reference_wrapper<U>, or assign-through semantics like std::tuple<U&>. JeanHeyd Meneide has a blog post from January 2020 on the subject: