result<>

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. The second (EC) is the type of object containing information about the reason for failure when the function fails. A result object stores either a T or an EC at any given moment, and is therefore conceptually similar to variant<T, EC>. EC is defaulted to std::error_code in standalone Outcome, or to boost::system::error_code in Boost.Outcome1. The third parameter (NoValuePolicy) is called a no-value policy. We will cover it later.

If both T and EC are trivially copyable, result<T, EC, NVP> is also trivially copyable. Triviality, complexity and constexpr-ness of each operation (construction, copy construction, assignment, destruction etc) is always the intersection of the corresponding operation in T and EC, so for example if both T and EC are literal types, so will be result<T, EC, NVP>. Additionally, if both T and EC have standard layout, result<T, EC, NVP> has standard layout; thus if both T and EC are C-compatible, so will result<T, EC, NVP> be C-compatible.

Now, we will define an enumeration describing different failure situations during conversion.

enum class ConversionErrc
{
  Success     = 0, // 0 should not represent an error
  EmptyString = 1, // (for rationale, see tutorial on error codes)
  IllegalChar = 2,
  TooLong     = 3,
};

// all boilerplate necessary to plug ConversionErrc
// into std::error_code framework
View this code on Github

Assume we have plugged it into std::error_code framework, as described in this section.

One notable effect of such plugging is that ConversionErrc is now convertible to std::error_code. Now we can implement function convert as follows:

outcome::result<int> convert(const std::string& str) noexcept
{
  if (str.empty())
    return ConversionErrc::EmptyString;

  if (!std::all_of(str.begin(), str.end(), ::isdigit))
    return ConversionErrc::IllegalChar;

  if (str.length() > 9)
    return ConversionErrc::TooLong;

  return atoi(str.c_str());
}
View this code on Github

result<T, EC> is convertible from any T2 convertible to T as well as any EC2 convertible to EC, provided that there is no constructability possible in either direction between T and EC. If there is, all implicit conversion is disabled, and you will need to use one of the tagged constructors:

outcome::result<int> r {outcome::in_place_type<std::error_code>, ConversionErrc::EmptyString};
outcome::result<int> s {outcome::in_place_type<int>, 1};
View this code on Github

Or use helper functions:

outcome::result<int> r = outcome::failure(ConversionErrc::EmptyString);
outcome::result<int> s = outcome::success(1);
View this code on Github

The functions auto failure(T &&, …) and auto success(T &&, …) return special types implicitly convertible to failed or successful result (and outcome).


  1. You can mandate a choice using std_result<T> or boost_result<T>. [return]