Search code examples
c++language-lawyeroperator-precedenceorder-of-execution

How does operator precedence affect order of evaluation?


How does operator precedence affect order of evaluation, not the difference between them? To better illustrate my question, I will include a short explanation of operator precedence below.

The following code is only for demonstrative purposes. Consider:

int i=++a*b+c/d/e

Some explanations will say that, because of operator precedence, * and / will be carried out before +, but I like to think of it as follows: If two operators with different precedence fight for the same expression, the one with the higher precedence gets the expression and forms a new one with it and its operands. Thus the top example is really:

int i=((++a)*b)+((c/d)/e)

This is known at compile time. At runtime order of evaluation kicks in and it decides whether a,b,c,d or i gets evaluated first. Which expression gets evaluated first is determined by sequencing, although most operators are unsequenced or indeterminately sequenced.

The reason C++ unsequenced operators instead of let's say a strict left to right sequencing rule is because it allows more room for optimization (e.g., operations can overlap and interleave) and having stricter sequencing rules would be detrimental to the performance of the program. There exists two separate sets of rules for value computation and side effects respectively. (1)

Therefore one important distinction to be made is the distinction between value computation and side effects. The sequencing of value computations is always determined by operator precedence and associativity.

The value computations (but not the side effects) of the operands to any operator are sequenced before the value computation of the result of the operator (but not its side effects).

Using the example, this means that the value of the expressions (++a) and b must be evaluated before (++a)*b, as without the knowledge of either a or b it is impossible to calculate the product of them, meaning that for value computations operator precedence directly influences order of evaluation.

Thus, in a C++ program that is made up of value computations exclusively, the evaluation order would entirely be dictated by operator precedence and associativity.(2)

Every time I explain it this way, I always get the question, if it were unspecified in which order the expressions c,d and e in ((c/d)/e) are evaluated, how does the operator precedence dictate evaluation order? To which I always respond that, without side effects, the order in which operands are evaluated doesn't make a difference, as long as they are evaluated before the value computation of the result of the operator. (3)

This however is not true for side effects. a++ has the side effect, that the value stored in a gets incremented by 1. Because side effects are generally not sequenced by operator precedence, this means that the increment in ++a can happen before, during or after the multiplication (++a)*b. Heck, it can even happen after the value computation of the entire expression right to the assignment operator ((++a)*b)+((c/d)/e).

Thus it is the side effects that can cause undefined behavior when sequenced incorrectly. Without side effects one can have as many value computations next to each other unsequenced as one want. (4)

In the expression ((c/d)/e) if c,d or e have side effects (for example, if they were functions modifying global values) then it does actually make a difference in which order they are evaluated even if they are evaluated before the value computation of the result of the operator.

The key takeaway would be that whenever somebody is talking about the difference between order of evaluation and operator precedence, they really mean "Value computations sequencing vs side effects sequencing".

To me, the distinction between value computation and side effect is essential to understanding the difference between order of evaluation and operator precedence correct. I have always explained it this way, however when looking at many explanations on the Internet most of them don't even mention this difference, with statements like "They are related but different".

It is confusing because the order of evaluation of value computations is very closely linked to operator precedence. It is the side effect computation that is independent of operator precedence.

This leaves me with some doubt about my own understanding:

Is my explanation above correct?

I want to specifically focus on paragraphs (1), (2), (3) and (4).


Solution

  • Thus in a C++ program that is made up of value computations exclusively the evaluation order would entirely be dictated by operator precedence and associtivity. (2)

    No, the evaluation order of value computations is not entirely dictated by operator precedence and associativity. They impose only a partial ordering on evaluation.

    Consider a + b. In this, a and b must be evaluated before a + b, but either a or b may be evaluated first.

    Also consider (a + b) * (c + d). Here, a + b and c + d must be evaluated before (a + b) * (c + d), but a, b, c, and d may be evaluated in any order. For example, a permissible order is b, d, c, c + d, a, a + b, (a + b) * (c + d).

    In general, with most operators, operator precedence and associativity create a tree structure for the expression, and each node on the tree must be evaluated before its parent but does not have any ordering requirement with nodes on other branches. Some operators, such as && and || impose additional constraints, such as that the left operand must be fully evaluated before any part of the right operand is evaluated, if the right operand is evaluated. And some operators require that one operand not be evaluated, such as sizeof (except, in C [not yet in C++], when its operand is a variable length array) or the conditional operator (one of b or c in a ? b : c is not evaluated).

    Regarding your other numbered paragraphs, I do not see any errors with them.