2017-05-28 0:13 GMT+02:00 Niall Douglas via Boost
This is in connection with Vicente's question: what exception safety should we expect of copy assignment of `expected`?
Vicente's paper promises a never empty guarantee. So if during a transition of state during assignment the new state's assignment operator throws, the previous state in the Expected is preserved.
Outcome master branch doesn't implement that. Outcome develop branch does.
First, let's consider the use case for `expected`, where we want to disable exception handling altogether. This means `T` or `E` cannot throw on any operation.
Vicente's paper allows T to throw during move or copy. E must be nothrow move. This makes possible the never empty guarantee.
So my view, as of today, is not to strive for a strong or even never-empty guarantee. Provide a conditional guarantee, if types T and E don't throw, you get no-fail guarantee. If they do, you only have a basic guarantee: you can destroy, assign to, or maybe call valueless_by_exception(). Nothing more. I think `std::variant` made the optimal choice.
std::variant could provide a never empty guarantee without increasing storage required if it imposed restrictions on its types e.g. all but one of the possible types must have nothrow move construction.
If it were allowed to double the storage required, it could use Anthony Williams' double buffer technique to ensure never empty guarantee. This eliminates the need for valueless_by_exception() entirely.
None of this affects Expected nor Outcome. Outcome hard codes EC and E, and both those types are guaranteed nothrow move constructible, so we can guarantee we never lose a previous state if assignment of a new state throws.
I'll ask the same I asked Peter, because I no longer see the value in never-empty guarantee. If I have two objecte of type variant, where A, B can throw on copy/move, and C is trivial: ``` variant a = A{}, b = B{}; try { a = b; // throws } catch(...) {} // at this point a holds a C ``` What good does it make to me that I had an A, wanted to assign a B and got a C? (unless C represents empty_state.) Same with outcomes: if I assign `o1 = o2` and because of an exception I get value different than `o1` or `o2` had initially, what good does it make? Of course, the bug in my examples is that I try to catch exceptions prematurely. If I let the stack unwinding continue, all these objects are gone and I can start anew. Regards, &rzej;