outcome<>

Type outcome<T, EC = varies, EP = varies, NoValuePolicy = policy::default_policy<T, EC, EP>> represents either a successfully computed value of type T, or one or two reasons for failure. Failure can be represented by EC, or EP, or both, although usually it will either be an EC or an EP. Similarly to result, EC defaults to std::error_code/boost::system::error_code, and EP defaults to std::exception_ptr/boost::exception_ptr.

The distinction is made into two types, EC and EP:

It should be emphasised that this distinction is by convention only, but it will be confusing to your users if you deviate significantly from this convention.


Legacy codebases

outcome is useful for transporting exceptions across layers of the program that were never designed with exception safety in mind.

Consider a program consisting of three layers:

graph BT L3["Layer3"] L2["Layer2_old"] --> L3 L1["Layer1"] --> L2

The highest-level layer, Layer3, uses exceptions for signalling failures. The middle layer, Layer2_old, was not designed with exception safety in mind and functions need to return information about failures in return value. But down in the implementation details, in Layer1, another library is used that again throws exceptions. The goal is to be able to transfer an exception thrown in Layer1 through Layer2_old, which is not exception-safe, and be able to rethrow it in Layer3.

In Layer1 we have two functions from two libraries: one reports failures by throwing exceptions, the other by returning result<>:

auto f() -> int;  // throws on failure
auto g() noexcept -> outcome::result<int>;
View this code on Github

In Layer2_old we cannot use exceptions, so its function h uses return type outcome<> to report failures. It is using functions f and g and reports their failures inside outcome<>:

auto old::h() noexcept -> outcome::outcome<int>
{
  OUTCOME_TRY(auto i, (g()));             // #1
    
  try {
    return i + f();
  }
  catch (...) {
    return std::current_exception(); // #2
  }
}
View this code on Github

#1. Operator TRY can forward failures encoded in result<T, EC> as outcome<T, EC, EP> without any loss in information. You can also use TRY to forward failure from one outcome<> to another.

#2. You can store the current exception through std::exception_ptr inside outcome<T, EC, EP> without any loss in information (provided that EP is std::exception_ptr).