Coroutine TRY operation

As one cannot call statement return from within a Coroutine, the very first part of Outcome’s support for Coroutines is OUTCOME_CO_TRYV(expr)/OUTCOME_CO_TRY(expr) , which is literally the same as OUTCOME_TRY() except that co_return is called to return early instead of return. eager<result<std::string>> to_string(int x) { if(x >= 0) { OUTCOME_CO_TRY(convert(x)); } co_return "out of range"; } »

Extending `OUTCOME_TRY`

Outcome’s OUTCOME_TRY(var, expr) operation is fully extensible to accept as input any foreign types. It already recognises types matching the concepts::value_or_error<T, E> concept, which is to say all types which have: A public .has_value() member function which returns a bool. In order of preference, a public .assume_value()/.value() member function. In order of preference, a public .as_failure()/.assume_error()/.error() member function. This should automatically handle inputs of std::expected<T, E>, and many others, including intermixing Boost. »

Inspecting result<T, EC>

Suppose we will be writing a function print_half that takes a std::string representing an integer and prints half the integer: outcome::result<void> print_half(const std::string& text); View this code on Github The type result<void> means that there is no value to be returned upon success, but that the operation might still fail, and we may be interested in inspecting the cause of the failure. The class template result<> is declared with the attribute [[nodiscard]], which means the compiler will warn you if you forget to inspect the returned object (in C++ 17 or later). »

TRY avoiding copy/move

OUTCOME_TRYV(expr)/OUTCOME_TRY(expr) works by creating an internal uniquely named variable which holds the value emitted by the expression. This implies that a copy or move operation shall be performed on the object emitted (unless you are on C++ 17 or later, which has guaranteed copy elision), which may be undesirable for your use case. You can tell OUTCOME_TRY to use a reference rather than a value for the internal uniquely named variable like this: »

TRY is greedy

OUTCOME_TRYV(expr)/OUTCOME_TRY(expr) has ‘greedier’ implicit conversion semantics than basic_result<T, E, NoValuePolicy> . For example, this code won’t compile: outcome::result<int, std::error_code> test(outcome::result<int, std::errc> r) { return r; // you need to use explicit construction here // i.e. return outcome::result<int>(r); } This is chosen because there is a non-trivial conversion between std::errc and std::error_code, so even though that conversion is implicit for std::error_code, Outcome does not expose the implicitness here in order to keep the implicit constructor count low (implicit constructors add significantly to build times). »

TRY operations

In the implementation of function print_half we have seen the usage of the macro OUTCOME_TRYV(expr)/OUTCOME_TRY(expr) : OUTCOME_TRY (auto i, BigInt::fromString(text)); The OUTCOME_TRY macro uses C macro overloading to select between two implementations based on the number of input parameters. If there is exactly one input parameter i.e. without the i, the control statement is roughly equivalent to: auto __result = BigInt::fromString(text); if (!__result) return __result.as_failure(); Where __result is a compile time generated unique name. »


We will define a function that converts a std::string to an int. This function can fail for a number of reasons; if it does we want to communicate the failure reason. outcome::result<int> convert(const std::string& str) noexcept; View this code on Github Template alias result<T, E = varies, NoValuePolicy = policy::default_policy<T, E, void>> has three template parameters, but the last two have default values. The first (T) represents the type of the object returned from the function upon success. »