outcome<T>, result<T> and option<T> all have formal empty states as part of their programming model. They are quite useful for a multitude of uses, I mainly have used them to early out from a sequence of monadic operations or to signal an abort from a noexcept function.
Would you mind going into more detail with respect to the use cases?
That would be tricky. The empty state is for whatever the programmer wants it to mean for some use case. It's a bit like returning a null pointer, that could mean failure, or success, or something else. Totally up to the programmer.
What does returning an empty result mean to the caller, conceptually? The idea of these result types is to return either a value or a reason for the lack of value, and an empty state is lacking both the value and the explanation for why the value is missing.
The major refinement of outcome<T>, result<T> and option<T> over expected<T, E> is that you **never** get undefined behaviour when using their observers like you do with expected<T, E>. So when you call .error() for example, you always get well defined semantics and behaviours. This is intended to let you skip manually writing boilerplate when using outcome<T>, result<T> and option<T>, and instead rely on the default behaviours. Let's imagine this: extern result<Foo> blah() noexcept; ... result<void> nah() { auto v = blah(); if(v) { Do something with v.value() ... } else { Do something with v.error() ... } } So if blah() returns success, we do something with the Foo returned, if blah() returns failure we do something with the error returned. Those are the two "normal" execution paths for binary success and failure. But let's say blah() wanted to abort nah(), it returns an empty result. That will test as boolean false, and when .error() is called you get a C++ exception thrown of type monad_error(no_state). auto v = blah(); if(v) { Do something with v.value() ... } else { Do something with v.error() ... throws if v is empty } In the above case empty causes a C++ exception throw, which will abort the caller's execution. You can however use that third state for any meaning you like. It's the same difference as ternary logic over boolean logic. If you don't want the empty state to cause a throw and simply be a third state handled normally: auto v = blah(); if(v.has_value()) { Do something with v.value() ... } else if(v.has_error()) { Do something with v.error() ... } else { result was empty ... } outcome<T>, result<T> and option<T> also have less-expressive to more-expressive implicit conversion, so if you feed an option<T> to something consuming an outcome<T>, it'll implicitly upconvert with no loss of information, including loss of any empty state. In larger code bases, it's very common to have low level code be using option<T> and result<T>, and higher level code using outcome<T> or C++ exception throws. The implicit convertibility without loss of original information and avoiding needless boilerplate was a major design goal for Outcome, as well as eliminating all the implicit reinterpret_cast<>'s which are in std::optional<T> and std::expected<T, E>. I appreciate that all that was quite hand wavy, and I didn't give a concrete use case. But I've used in my own code the empty state for everything from an assert failure through to checking if a search found something e.g. outcome<Foo> found; // default constructs to empty for(auto &i : container) { auto v = something(i); // returns a result<Foo> if(v) // if not errored { found = std::move(v); // auto upconverts break; } } if(!found) { die(); } It's really hard to be more specific. Do ask if the above doesn't make sense to you. It's midnight as I type this, I am probably being unclear. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/