On 27/11/2015 06:18, Domagoj Šarić wrote:
Sorry, I misunderstood you...thought you were talking about converting to Ts _inside_ the combiner function... Hm..right I missed that this is actually a shady area in the case of multiple function parameters...probably because I just assumed that, even though the order of computation of individual parameter values is unspecified, each value would be fully computed before the compiler moves onto the next one...Trying to see what the standard actually says (n3797 draft @ 1.9.15): "When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [Note: Value computations and side effects associated with different argument expressions are unsequenced. —end note]"[1] -> this end note might be interpreted as implying the negative, i.e. that value computations and side effects associated with _same_ argument expressions are _not_ unsequenced ... IOW that operations/instructions producing the value of parameter1 may not be interleaved with those producing the value of parameter2 (although the order whether parameter1 or parameter2 is produced first is left unspecified).
No, that simply says that all parameters must be fully evaluated before the first instruction of the called function executes. It does not say anything about the sequencing of the parameter evaluation prior to the actual call. As far as I am aware, function parameters may be evaluated in any order, including decomposed orders. By that I mean that in the expression f(a.b, c().d()), then you are guaranteed that a is evaluated before b and c() before d(), and both a.b and c().d() before *the actual call of f*, but you have no guarantees about the order of the evaluation of f to a method pointer vs. a vs. c(). So the compiler is perfectly free to evaluate c() first, then a, then f, then b, then d(), and then finally call the method that f evaluated to. Or several other such combinations.
I may very well be completely mistaken in this amateur 'exegesis' but, even if I am wrong and 'complete/non-interleaved' parameter values computation is not guaranteed fallible_result<> can still be made to work with the desired or 'good enough' [2] semantics by allowing multiple fallible_results to exist (removing the related asserts) and inserting an if (!std::uncaught_exception()) check before throwing (Scot Meyers' std::uncaught_exception() related gotw does not apply here). I can then improve the debugging logic to assert that at least one of the multiple fallible_results was inspected before leaving the current scope thereby still catching the contrived case of unexamined/untouched/unused local fallible_result variables 'hidden&forgotten' in autos. I say contrived because this is still better than what you currently have with 'regular' return codes, i.e. you don't get even an assertion for unexamined results. You do get compiler specific warnings about unused variables but you'd get those just as well for fallible results [3].
Yes, that would probably be an improvement.
Yes, within the function that gets called the && reference parameter is an lvalue, not an rvalue, since it has a name. But all it takes to make it an rvalue again is a call to std::move.
And this does not seem like unreasonable behaviour in itself, if that only occurs once (perhaps to construct the result_or_error within the function).
I'm not sure what you are getting at here...
I was referring to the hypothetical combiner function that accepted multiple fallible_result<>&& parameters (since it consumes all of them), and then returns its own fallible_result from one or more of them. Within the function it will not be able to call any methods on the fallible_result<>&& parameters (since they're actually lvalues now), except for one very common case: auto a = std::move(paramA).as_result_or_error(); (And that this construct is not unreasonable as long as paramA is not accessed after this point -- it's no different from any other move.) I was also referring to the compiler errors that would be generated from "improper use" being the same ones that people are likely to reflexively add std::move() calls to resolve.
The only way it works is to explicitly separate out the calc calls, either as:
foo_error_t a = calcA(); foo_error_t b = calcB();
or as:
auto a = calcA().as_result_or_error(); auto b = calcB().as_result_or_error();
(and woe betide you if you accidentally use "auto" in the first case)
What woe? You'd get a compiler error if you 'used auto in the first case' and tried to send a and b to 'combiner' (or pretty much do anything else with them)...
The woe is that you'd also get a runtime assert since two fallible_results managed to exist at the same time again. But yes, that could only happen if you forgot to actually use them, or if you thought the errors meant that you were supposed to add std::move(). It does make the behaviour a little strange for the various cases though: calcA(); // throws because fallible_result went uninspected calcB(); -------------- auto a = calcA(); auto b = calcB(); // asserts because two fallible_results exist // compiler error if a or b are used later without std::move -------------- foo_error_t a = calcA(); foo_error_t b = calcB(); // no errors even if a or b go unused after this point // a & b are either valid or have error codes -------------- foo_t a = calcA(); // throws if calcA has an error foo_t b = calcB(); // throws if calcB has an error // a & b are both valid if you survive this far -------------- auto a = calcA().as_result_or_error(); auto b = calcB().as_result_or_error(); // no errors even if a or b go unused after this point // a & b are either valid or have error codes -------------- something(calcA(), calcB()); // asserts for two fallible_results -------------- something(calcA().as_result_or_error(), calcB().as_result_or_error()); // might work or might assert depending on the compiler's mood