When I looked at the compiler support for cppreference recently, I found that libstdc++ already implements <expected>, but unfortunately I didn’t find much information about it on the web.

What is std::expected?

It is similar to std::optional, but std::optional can only indicate a normal value or std::nullopt, i.e. a null value. In contrast, std::expected can indicate an expected value and an error value, which is equivalent to the two-member std::variant, but is more convenient to use on the interface. Think of it as a new kind of error handling.

Basic use

Constructs

std::expected<T,E> has two template arguments, the first indicating the expected value and the second indicating the wrong value.

If it is the expected value, there is an implicit conversion.

1
std::expected<int, std::string> e = 42;

If it is an exception, it is initialized by std::expected().

1
std::expected<int, std::string> e = std::unexpected("Error");

default constructor

1
std::expected<S, int> e;

Requires S to have a default constructor, otherwise an exception will be thrown.

Usage

Same pointer semantics as std::optional, can be dereferenced.

1
2
3
std::expected<int, std::string> e = 42;
if (e)
  std::cout << *e << "\n"; // prints 42

Note that you must check before unquoting, otherwise it is UB!!!

The following is equivalent in effect to the above.

1
2
3
std::expected<int, std::string> e = 42;
if (e.has_value())
  std::cout << e.value() << "\n"; // prints 42

Note that std::expected<T,E>::value() throws an exception if the value does not exist, which is somewhat safer.

Indicates an error

1
2
3
std::expected<int, std::string> e = std::unexpected("Error");
if (!e.has_value())
  std::cout << e.error() << "\n"; // prints `Error`

If not used incorrectly as UB!!!

If the result returned is said to be an error, a default value can be provided more elegantly with value_or().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
std::expected<int, std::string> MayHasErr(int i) {
    if (i < 0)
        return std::unexpected("Error");
    else
        return i * 2;
}

int main() {
    std::cout << MayHasErr(3).value() << "\n"; // print 3
    std::cout << MayHasErr(-2).value_or(42) << "\n"; // print 42
}

Internal implementation

The implementation of std::expected<T,E> is relatively simple and does not rely on any new language features. It consists of an expected value of type T and a union of std::unexpected<E> and a bool value indicating whether it is an error or not. The rest are some helper functions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
template<class T, class E>
  class expected {
  using unexpected_type = unexpected<E>;
   private:
    bool has_val;
    union {
      T val; // 表示期望的值
      E unex; // 表示异常的值
    };
  };