What does left-to-right associativity mean?

后端 未结 6 683
生来不讨喜
生来不讨喜 2020-12-29 21:23

I am confused about the definition of left-to-right and right-to-left associativity. I have also seen them called left associativity and right associativity and would like t

6条回答
  •  醉梦人生
    2020-12-29 22:21

    An infix operator (or more generally expression type that has unclosed left and right sub-expressions) is left associative if in nested use of this operator (expression type) without explicit parentheses, the implicit parentheses are placed at the left. Since * is left-associative in C++, a*b*c means (a*b)*c. In case of deeper nesting, a cluster of implicit parentheses occurs on the left end: (((a*b)*c)*d)*e.

    Equivalently this means the syntactic production rule for this operator is left-recursive (meaning the left sub-expression has the same syntactic category as the one this rule is a production for, so that the same rule (same operator) may be used directly to form that sub-expression; the sub-expression at the other end has a more restrictive syntactic category, and using the same operator there would require explicit parentheses). In C++, one production for multiplicative-expression (section 5.6 in the Standard) reads mutliplicative-expression * pm-expression, with multiplicative-expression on the left.

    Consequently, in a nested use without explicit parentheses, the leftmost operator takes its immediate neighbours as operands, while the other instances take as left operand the (result of) the expression formed by everything to their left.

    I admit, I've been pushing this a bit (too far). My point in that nowhere above does the word "right" occur, nor is there any movement involved; associativity is a syntactic and therefore static matter. It is important where the implicit parentheses go, not in which order one writes them (in fact one does not at all, or else they would be explicit). Of course, for right-associativity, just replace each "left" by "right" above.

    In conclusion, I can see no valid reason why one should call this left-to-right associativity (or grouping), but the fact is that people do (even the Standard does, though it is perfectly redundant given that explicit syntax rules are also given).

    Confusion comes from explaining this, as is often done, by saying that (in absence of explicit parentheses) the operators are performed from left to right (respectively from right-to-left for right-associative operators). This is misleading because it confuses syntax with semantics (execution), and also is only valid for operations with bottom-up evaluation (all operands are evaluated before the operator is). For operators with special evaluation rules it is simply wrong. For the operators && (and) and || (or) the semantics is to evaluate the left operand first then the operator itself (namely decide whether the left or the right operand will produce the result) followed possibly by the evaluation of the right operand. This left-to-right evaluation is totally independent of the associativity: the operators happen to be left-associative, probably because all non-assignment binary operators are, but (c1 && c2) && c3 (with redundant parentheses where they would already implicitly be) has an equivalent execution to c1 && (c2 && c3) (namely execute the conditions from left to right until one returns false and return that, or if none does return true), and I cannot imagine a reasonable compiler generating different code for the two cases. Actually I find the right-grouping more suggestive of how the expression is evaluated, but it really makes no difference; the same goes for or.

    This is even more clear for the conditional (ternary) operator ? ... :. Here associativity applies, because there are open sub-expressions at both sides (cf. my opening sentence); the middle operand is enclosed in ? and : and never requires additional parentheses. Indeed this operator is declared to be right-associative, which means that c1 ? x : c2 ? y : z should be read as c1 ? x : (c2 ? y : z) rather than as (c1 ? x : c2) ? y : z (the implicit parentheses are to the right). However, with the implicit parentheses the two ternary operators are executed from left to right; the explanation is that the semantics of the ternary operator does not evaluate all sub-expressions first.


    Back to the example from your question, left-associativity (or grouping left-to-right) means that your matrix-vector product is parsed as ((M1*M2)*M3)*v. Although mathematically equivalent, it is virtually impossible that this gets executed as M1*(M2*(M3*v)), even though that is more efficient. The reason is that floating-point multiplication is not truly associative (just approximately), nor is therefore floating-point matrix multiplication; the compiler therefore cannot transform one expression into the other. Note that in ((M1*M2)*M3)*v one cannot say which of the matrices gets applied to a vector first, because none of them is: the matrix of the composite linear maps gets computed first, and the that matrix gets applied to the vector. The result will be approximately equal to that of M1*(M2*(M3*v)) in which M3 is applied, then M2 and finally M1. But if you want things to happen like that, you have to write those parentheses.

提交回复
热议问题