What is std::expected in C++?
Asked Answered
T

1

36

In one of the most respected stackoverflow answer I found an example of std::expected template class usages: What are coroutines in C++20?

At the same time I cannot find any mentioning of this class on cppreference.com. Could you please explain what it is?

Tumefacient answered 13/7, 2021 at 19:50 Comment(6)
It's a fantasy - may not happen - but .. may stillWittol
you can't find it on cppreference because it is not a part of the standardLeatrice
Some info about it: github.com/eugnsp/library/blob/master/cpp/…Paulino
It's still only a proposal wg21.link/p0323 - respected answer or not, everybody injects their opinions into answers, whether explicitly or implicitly. In this case, it was the opinion that std::expected should (and would) be standardized soon.Photoactive
Like others had mentioned, expected was only proposed. But there are some user written ones you can lookup: github.com/TartanLlama/expectedElectrograph
en.cppreference.com/w/cpp/error/unexpectedAchorn
P
63

Actually, the best way to learn about std::expected is a funny talk by the (in)famous Andrei Alexandrescu: "Expect the Expected!"

What std::expected is, and when it's used

Here are three complementing explanations of what an std::expected<T, E> is:

  • It is the return type of a function which is supposed to return a T value - but which may encounter some error, in which case it will return a descriptor of that error, of type E.

    An example:

    std::expected<ParsedData, ParsingError> parse_input(Input input);
    
  • It's an error-handling mechanism, being an alternative to throwing exceptions (in which case you always return the value you were supposed to), and to returning status/error codes (in which case you never return the value you want to, and have to use an out-parameter).

    Here are the two alternative error-handling mechanisms applied to the function from the previous example:

    ParsedData    parse_input_2(Input input) noexcept(false);
    ParsingError  parse_input_3(ParsedData& result, Input input);
    
  • It's a discriminated union of types T and E with some convenience methods.

"How is it better than just an std::variant<T,E>?"

It behaves somewhat like std::optional<T>, giving focus to the expected, rather than the unexpected, case:

  • result.has_value() - true if we got a value rather than an error.
  • if (result) - checks for the same thing
  • *result - gives us the T value if it exists, undefined behavior otherwise (same as std::optional, although many don't like this).
  • result.value(), gives us the T value if it exists, or throws otherwise.

Actually, that last mode of access behaves differently than std::optional if we got an error: What it throws is a bad_expected_access<E>, with the returned error. This behavior can be thought of as a way to switch from expected-based to exception-based error-handling.

"Hey, I looked for it in the standard and it isn't there!"

std::expected will be part of the upcoming C++23 standard. The proposal (P0323) has recently been accepted.

This being said - it is quite usable already, since it requires no new language facilities:

  • I can recommend one Sy Brand (tartanllama)'s implementation, which can be used with C++11 or later. It also has some neat functional-style extensions (which may not be standardized).
  • @Malachi recommends estd::expected, part of a standard library implementation intended for embedded environments.
Pronunciation answered 3/1, 2022 at 20:50 Comment(4)
I really don't get why *result does not check for a valid value while result.value() does. Maybe they want you to use them the way you use a "raw pointer" that might be nullptr. Or, maybe they are worried with efficiency. I do not find that use the same way as you use a "raw pointer" is something good... we want to get rid of them, don't we? If the problem is efficiency, the roles could just be swapped. If you area lazy person and don't want to check, just use *result and wait for the throw. If you are concerned, you can use result.value().Gristede
@AndréCaldas: Well, we need both a way to access the value with a check, and a way to access it without a check - for reasons of both expressivity and performance/avoiding overhead.Pronunciation
Yes! We agree on that. It is just that *result should be the way that throws. People say that we should have less "undefined behaviour". Those really worried about performance should be the ones that do not use *result they should use some result.get_with_no_check(). This would be much less error prone.Gristede
@AndréCaldas: Well, you have an argument with the standards committee. They decided it will be the other way around. I happen to think their choice is intuitive via the analogy to a pointer (i.e. dereferencing without a null check may cause undefined behavior), but your suggestion is not without merit.Pronunciation

© 2022 - 2024 — McMap. All rights reserved.