Le 18/11/2015 06:49, Gavin Lambert a écrit :
On 17/11/2015 21:06, Domagoj Saric wrote:
Also, if the fallible_result rvalue is left uninspected and contains an error its destructor will throw (which AFAICT should be safe considering it is an rvalue and no other exception can possibly be active at the same time)
Throwing from a destructor can cause abrupt termination (or undefined behaviour in some compilers); there are no conditions in which it should be considered "safe".
That's not mandated by the standard...that would be a broken compiler/standard library...
It's mandated by the standard if another exception is already in flight.
Additionally I'm not convinced that no exception can be active at the same time. Consider function call parameters -- you could easily have one of these get constructed but not consumed yet, and then another parameter throws an exception, resulting in destruction of your object during the throw of an exception.
That can't happen: fallible_results are not meant for passing parameters, and thanks to the rvalue semantics this is enforced by the compiler rather than just by convention - even if you go to some lengths to declare a function as taking a fallible_result (i.e. completely contrary to what the type is for) foo( fallible_result
bar ); (instead of foo( bar ); ) you'll soon discover that you are doing something wrong because the compiler won't let you do anything with that bar... (and if the standard would let me declare a special && destructor for rvalues there'd be no end to happiness, i.e. you'd get an error immediately, at the declaration, and even better codegen in somecases...) Declaring a parameter as fallible_result<T>&& will satisfy the compiler, and this doesn't seem unreasonable if it's being passed to a helper method intended to process such results.
Imagine a function that calculates several results, some of which may fail, and then calls a combiner method to choose the "best" non-failing result of the set. Wouldn't the combiner naturally expect a fallible_result<T>&& as its inputs, since it wants to consume them?
I suppose you could force it to accept result_or_error<T> instead, but since this isn't the return type of the function it inhibits using generic type-deducing template code with simplified expressions like: return combiner(calcA(), calcB(), calcC());
The more problematic case is if the combiner was not expecting failure, and so someone used the same expression with a combiner that accepted T. So the compiler calls all three calc methods (constructing fallible_result<T>s along the way), then gets around to converting the first one back to T, which throws. This is ok, but then the other two are destroyed; and if at least one of these throws too, then you're dead.
Sure, you can tell people to only use this as a standalone expression and not as a function parameter, but this seems very fragile. People like to inline things, especially rvalues.
Your example can be adapted to several variables of type faillible_result<T> auto a = calcA(); auto b = calcB(); auto c = calcC(); if (a.value()) // can throw :( I like the intent of faillible_result<T>, but as you described it could be more dangerous than safe. Thanks for reporting this case. It helps me a lot. Vicente