Evaluation order of index expressions

Jonathan M Davis via Digitalmars-d digitalmars-d at puremagic.com
Mon May 25 08:35:00 PDT 2015


On Monday, 25 May 2015 at 12:38:29 UTC, kinke wrote:
> On Monday, 25 May 2015 at 08:00:15 UTC, Jonathan M Davis wrote:
>> It might be completely well-defined and consistent, but it may 
>> not be what you expect, and even if it is, a slight change to 
>> the code could change the order.
>
> If the behavior isn't what I expect (and I don't think that's 
> often case for left-to-right order), then the language should 
> force me to express the intention differently. If it's not 
> well-defined, I may not be aware of such issues until I use a 
> different compiler.

It seems like you still don't understand. Yes, defining the order 
of evaluation within an expression as being left-to-right makes 
it easier to deal with what is directly inside the expression, 
and the compiler could be make to do that (and from the sounds of 
it likely will). It could also be made to not be fixed about the 
order of evaluation but give an error when it detects that the 
order of evaluation matters, so that expressions like

foo(++i, ++i);

give an error. But even so, the compiler _cannot_ be made to 
catch all such problems for you, because all it takes is starting 
to bury the problem within other function calls within the 
expression, and while the order of evaluation in such cases may 
very well be defined, whether it's going to do what you expect is 
a completely different matter. For instance, what if you had

int bar()
{
     return ++i;
}

foo(bar(), bar());

Now, because ++i is inside a call to another function, the 
compiler can no longer see that the arguments that you're using 
depend on one another. The results _are_ well-defined, but 
whether it's what you expect is another matter. With one extra 
layer like this, you'll probably see it, and it'll be doing what 
you want, but what if you have an expression like

auto f = foo(bar() + baz(bop(), beep() + boop()));

and 4 levels down into the call stack bar() and beep() both 
mutate a static or global variable - or some other shared 
resource. Then the result of this expression ends up depending on 
an order of evaluation issue that you can't see without really 
digging into the code, and the compiler sure isn't going to see 
for you. You might see that swapping the arguments around in the 
expression results in a different result when you think that it 
shouldn't, but just as likely, you wouldn't catch that, and a 
small change to the code later could change the results 
unexpectedly, which you might or might not notice.

Now, that sort of thing is all the more reason to avoid using 
static or global variables, and it's the sort of thing that I 
would hope good code would avoid. But defining the order of 
evaluation as left-to-right, doesn't make those problems go away. 
At best, it makes them consistent, and that may be worth it, but 
it's not a silver bullet. And it takes optimization opportunities 
away from the compiler, since in many cases, it can reorder how 
the expression is evaluated to make better use of the registers 
and whatnot. So, forcing the order of evaluation is not without 
its cons, even if it did solve all order of evaluation issues, 
and it really doesn't - especially when that often depends on 
what you expect.

Ketmar had a screwy example with arrays a while back that he was 
complaining bitterly about not working due to order of evaluation 
issues, but IIRC he had recursive function calls which affected 
each other and was having all kinds of problems just because he 
insisted on doing multiple things in an expression rather than 
splitting them out. And the results he was getting were 
completely consistent; they just weren't what he wanted. The 
order of evaluation mattered too much in the expressions that he 
was writing.

Ultimately, if you want to reduce the risk of bugs, you really 
should be writing expressions where the order of evaluation 
doesn't matter, or where it only matters based on operator 
precedence directly within the expression so that it really 
doesn't matter what other code is doing. And forcing 
left-to-right evaluation doesn't change that. All it really does 
is make what what's happening consistent. It doesn't mean that 
relying on it is a good idea or that it's going to fix anything 
but the some of the most basic order of evaluation issues.

Personally, I don't think that it's worth the cost in lost 
optimizations, but even if it is, my point here is really that at 
best it only fixes part of the problem.

- Jonathan M Davis


More information about the Digitalmars-d mailing list