A result type for C++
I wrote my own version of the very useful ‘result type’ pattern for C++.
The goal of a result type is to provide one of two specific kinds of value as the result of an operation. One representing the typical, or expected outcome. And the other kind representing a secondary or unexpected outcome.
The primary use case is in error handling. If an operation has a more complex error state than a mere boolean flag, then this pattern works very well. A simple example would be a parser. The primary result would be the parsed data structure, while the error would be a detailed list of error messages.
This already exists in Rust through Result<T, E>, which integrates nicely with the overall design of that language.
C++23 also has std::expected, but it is relatively new and has extremely baroque syntax, as is tradition…
The following is my version of the concept, which is valid for C++20. It’s less than 50 lines of code. It’s reasonably efficient. It’s very readable and can be copied easily into an existing codebase.
template <typename Error>
struct failure { Error value; };
template <typename Error>
failure(Error&&) -> failure<std::decay_t<Error>>;
template <typename Value, typename Error>
class result
{
public:
constexpr result(Value v) : m_data(std::in_place_index<0>, std::move(v)) {}
constexpr result(failure<Error> e) : m_data(std::in_place_index<1>, std::move(e.value)) {}
constexpr result& operator=(Value v) { m_data.template emplace<0>(std::move(v)); return *this; }
constexpr result& operator=(failure<Error> e) { m_data.template emplace<1>(std::move(e.value)); return *this; }
constexpr explicit operator bool() const noexcept
{ return isValue(); }
constexpr Value& operator*() { return value(); }
constexpr Value* operator->() { return &value(); }
constexpr Value const& operator*() const { return value(); }
constexpr Value const* operator->() const { return &value(); }
constexpr bool isError() const noexcept { return m_data.index() == 1; }
constexpr bool isValue() const noexcept { return m_data.index() == 0; }
constexpr Value& value() { return std::get<0>(m_data); }
constexpr Error& error() { return std::get<1>(m_data); }
constexpr Value const& value() const { return std::get<0>(m_data); }
constexpr Error const& error() const { return std::get<1>(m_data); }
constexpr Value valueOr(Value def) const
{ return isValue() ? std::get<0>(m_data) : std::move(def); }
constexpr Error errorOr(Error def) const
{ return isError() ? std::get<1>(m_data) : std::move(def); }
private:
std::variant<Value, Error> m_data;
};Here are a few specific design choices:
Errorvalues must be wrapped by afailurestruct. This allows for declarations likeresult<int,int>and makes instantiating theErrorvalue more explicit, and thus more readable.- Follows STL conventions for accessing the
Valuevalue. - Leverages existing
std::variantfrom STL for the sake of simplicity.
Overall it is quite ergonomic to use, as you can see here.
result<int, char const*> parse(string s)
{
if (s == "a") return 1;
if (s == "b") return 2;
if (s == "c") return 3;
if (s.empty())
return failure("s is empty");
return failure("s is invalid");
}
int main(int argc, char** argv)
{
if (auto r = parse(""))
cout << "res: " << *r << endl;
else
cout << "error: " << r.error() << endl;
if (auto r = parse("b"))
cout << "res: " << *r << endl;
else
cout << "error: " << r.error() << endl;
return 0;
}And that’s it, pretty simple. I mostly did this for fun, but I will be using it in my current project, and otherwise keep it as a new tool that I can use in the future.
Did you find this useful? Do you have any little C++ tricks of your own? If so, Let me know!
Send a message to mail@lzon.ca, or DM me on one of my social accounts on the homepage.