
On 1/02/2018 19:55, Emil Dotchevski wrote:
This does not protect the user at all:
std::unique_ptr<A> a=A::create(); a->foo(); //undefined behavior
Sure, but you can always write daft code. If you are aware that it is legal for A::create() to return nullptr (and you should be aware, because it will be documented), then the above would be an obvious bug, and you should replace it with: if (auto a = A::create()) { a->foo(); } This is *much* more obviously safe than your suggested exception-enabled code, where you have to have external knowledge (that it will throw on failure and cannot ever return nullptr). And it's trivial to add logic for the failure case in an else block, vs. the more wordy try-catch. (Even if you don't know whether A::create() is supposed to be able to return nullptr or not, it is usually more sensible to defensively write the code as if it could, since it is a legal value of the return type, regardless of whether exceptions are enabled or not.) Granted, exceptions are better at telling you *why* it failed, which the above can't. And exceptions are better at hiding the error-handling paths for the cases where errors are expected to be exceptionally rare (pun intended), which can make the "real" logic not get lost in a maze of error-handling. Most people appreciate that -- but sometimes having explicit error paths is useful too. Nobody is forcing you to stop using exceptions, or even encouraging the majority of applications to stop using them. Outcome just provides a way to add back a little (or a lot, as needed) explanation to failure in cases where someone has already decided they can't or don't want to use exceptions for whatever reason -- especially when failure is expected, not unusual (I think everybody agrees that exceptions-as-control-flow is not good code design, right?).
This is not an advantage over using exceptions, and besides it is only true in the simplest of cases. The problem is that Outcome requires users to fit any and all possible failures into a single error type, which is not always possible. In general, you can neither enumerate nor reason about all failures which may occur. Notably, the one Outcome feature that can deal with this problem is its ability to transport std::exception_ptr. :)
One error type per context. You can have completely different error enumerations per method if you really want to, although it would probably be more common to have one error domain per library, or just use the closest approximation from a standard set, like POSIX errno. Or use error_code, which allows relatively seamless interoperation with all of these. Exceptions are actually a relatively poor method for transporting errors that need to be *reacted* to. (Not so much through any fault of exceptions themselves, but rather through common usage thereof.) In most code, the best you can do with them is catch them in some central place and log them for some developer to look at later. Too many methods just throw one of the standard types like std::runtime_error without defining custom subclasses or adding anything more programmatically useful beyond the text message -- which in itself can be a problem for multi-lingual applications, especially where the thread that generated the exception is running in a different language context from the developer and/or user. At least an error code is inherently an abstract thing that you know you need to look up a translation for at the last moment when actually presenting it to the user, or that logic can easily say "in this context, I was expecting that and I know how to deal with it". Sure, it's possible to add error codes and extra data fields to exceptions and allow the message to be constructed later from these pieces. And it's not possible to do this with error_code alone. But it will be possible to do this with Outcome. (And most people don't do it anyway, no matter the error transport.)