Niall Douglas wrote:
Now, some may say that calling .exception() on an errored object is wasteful because you must silently construct an exception_ptr for a system_error. But equally, I'd counter that if you actually care about that, check .has_error() and yank out the error_code before calling .exception().
How I see things here is, you specify what error() and exception() return after calls to set_value(v), set_error(e), and set_exception(x), respectively, as if conceptually initialization stores the appropriate return values into independent error_ and exception_ member variables. Then, the synthesis of an exception_ptr from the error code in exception() is just an optimization that doesn't change behavior but is just a different tradeoff - it favors copy performance (no exception_ptr to copy after set_error) at the expense of exception() performance (have to make_exception_ptr each time.)
After all:
sizeof(error_code_extended) = 24 sizeof(exception_ptr) = 8
So for a type predominantly used solely for returning results from functions where you will only ever be returning one of them at a time, there is a very strong argument in favour of ditching the variant storage in exchange for considerable improvements in:
- implementation complexity - compile times for end users - load on the compiler's optimiser
... for the hardcoded EC = error_code_extended and E = exception_ptr Outcome.
I see this as a promising direction because it would allow you to store both an error and an exception, as per the other thread. Then you can fully represent a Filesystem function with an outcome return value, because you'll be able to store the error_code return in error() and the filesystem_error exception in exception(). The actual behavior of Outcome won't change that much, because if you only look at its observable state as told by the accessors, logically it's not a variant, because both has_error and has_exception report true at the same time. In fact has_error == has_exception if I'm not mistaken?