C++ Logo

std-proposals

Advanced search

P0323 std::expected - void value type?

From: Emile Cormier <emile.cormier.jr_at_[hidden]>
Date: Sun, 4 Oct 2020 18:20:40 -0300
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).

The spec for std::expected is currently very complex with constraints on
overloads. expected(const expected<U, G>& rhs) in particular is quite scary
and makes me wonder if it can slow down compilations times. Not having to
worry about the T=void case would simplify things.

If a user wants to use std::expected to indicate a success-or-failure
result (with no value), they could simply use something like std::monostate
as the value type. What I did in my toy result<T,E> implementation is
provide an empty success_t tag type that can be used when there is no
meaningful value to return. For example:

struct success_t
{
    constexpr success_t() = default;
    constexpr bool operator==(success_t) const {return true;}
    constexpr bool operator!=(success_t) const {return false;}
};
inline constexpr success_t successful;

result<success_t, std::error_code> write(const std::string& str)
{
    if (writeToDeviceWorks(str))
        return successful;
    else
        return
with_failure(make_error_code(std::errc::device_or_resource_busy));
        // with_failure is my equivalent of unexpected(), where I don't
have the
        // luxury of CTAD in C++11.
}

int main()
{
    auto res = write("foo");
    if (res == successful)
        std::cout << "Yay!\n";
    else
        std::cerr << "Oh no: " << res.error() << "\n";
}

The "successful" object has the benefit that it's quite readable when used
on the RHS of operator==, as shown above.

The one benefit of allowing T=void I can see is that a std::expected<void,
E> specialization can get rid of its union storage and directly store
error_type. Is this the rationale for allowing std::expected<void, E>?

Cheers,
Emile Cormier

Received on 2020-10-04 16:20:52