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).

If this is the main problem, then partial specialisation of your expected<> for void can solve the issue. You can have a look at what I mean in my implementation of Result<Ok, Err> here.

On Sun, Oct 4, 2020 at 10: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'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
--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals


--
Dmitry
Sent from gmail