15

The C++ operator precedence table from http://en.cppreference.com/w/cpp/language/operator_precedence (I know it's not normative, but the standard doesn't talk about precedence or associativity) marks unary operators as right/left associative.

From a discussion on a different question, I'm left with doubts. Does it make sense for unary operators to be associative?

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • see my answer here (http://stackoverflow.com/a/14084830/1740808) with an example of hyphotetical language having left-associative prefix operators – SergeyS Dec 30 '12 at 08:22

4 Answers4

11

It's just an artefact of the way that the associativity is derived from the grammar.

The reason that addition is left-associative is that one of the productions for additive-expression is additive-expression + multiplicative-expression, with the additive-expression on the left. So when you see:

a + b + c

this must be equivalent to (a + b) + c, because the only way to match the production is with a + b as the additive-expression and c as the multiplicative-expression. a on its own is an additive-expression, but b + c is not a multiplicative-expression and so a + b + c doesn't match the production if we try to take a as the additive-expression.

If you haven't before, I recommend that you read through the "Expressions" chapter ignoring the semantics: look only at the grammar productions. Then you'll see just how it is that precedence and associativity are defined by the grammar. The big trick is that every "high-precedence" type of expression IS-A "lower-precedence" type of expression. So every multiplicative-expression is an additive-expression, but not vice-versa, and this is what makes multiplication "bind tighter" than addition.

Prefix unary operators are defined in the grammar like: unary-expression: ++ cast-expression and so on, with the operator on the left for prefix and on the right for postfix. In other words, we "insert the parentheses" on the left for postfix and on the right for prefix. That is, we can say that the grouping is left-to-right for postfix operators and right-to-left for prefix operators. And indeed the C++ standard says exactly that (5.2/1 and 5.3/1 in C++03). It might be an abuse of terminology or at least a new coinage to refer to this unary grouping as "associativity". But it's not a major one since it's obvious what must be meant.

The only difference here between binary and unary operators is that the syntax would still make sense if the binary operators grouped in the opposite direction, so a - b - c means a - (b - c). It would be surprising but would not otherwise affect the language. With unary operators it would be more than surprising to group !!a as (!!)a, the language would also have to supply a meaning for the sub-expression !!, which currently it doesn't have. A functional language could give it a meaning: !! might mean the function composed from ! and !, i.e. the same operation as static_cast<bool>(), but C++ has no concept of composing functions or operators. The reason C++ doesn't need to supply that meaning is that ! "groups right-to-left". Which (because of the big trick in the grammar) is just another way of saying that !! is not a syntactically correct expression so is never a sub-expression of anything.

So yes, it does make sense to say that prefix operators group right-to-left and postfix operators group left-to-right. But it's also "obvious" that it must be this way around, because of other things we know about the C++ language.

Btw, I think that technically speaking in C++ at least, postfix ++ is not a unary operator. It's a postfix operator. But that really doesn't matter except that it's the terminology in the standard, because obviously it is an operator and it has one operand, so is "unary" in English.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • see my answer here (stackoverflow.com/a/14084830/1740808) with an example of hyphotetical language having left-associative prefix operators – SergeyS Dec 30 '12 at 08:23
2

The associativity of the operator in the case of unary operators just determines which side of the operand the operator appears on.

Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
  • 2
    @LuchianGrigore and they are different operators. – Seth Carnegie Oct 18 '12 at 18:34
  • Ah, yes, they appear 2 times - suffix before prefix. – Luchian Grigore Oct 18 '12 at 18:35
  • so, bottom line, they are associative, only not in the same sense as the rest? – Luchian Grigore Oct 18 '12 at 18:39
  • 1
    @LuchianGrigore yes, because they can't have the same ambiguity as binary operators, but you do have to figure out what operand the unary operator applies to when it is between two possible operands. – Seth Carnegie Oct 18 '12 at 18:43
  • Can you give an example of an ambiguity? – Luchian Grigore Oct 18 '12 at 18:47
  • @LuchianGrigore the ambiguity I'm talking about is when you have `a - b - c`, is it `(a - b) - c`, or `a - (b - c)`? The two can give different answers. Associativity rules say it's `(a - b) - c` since `-` is left-associative. – Seth Carnegie Oct 18 '12 at 18:48
  • @LuchianGrigore, consider `a + b + c` and `a = b = c`. They are grouped differently, because `+` is left-associative, whereas `=` is right-associative. On the other hand, there's only one way to parse `+ + i`. – avakar Oct 18 '12 at 18:49
  • Yes, but in this case `-` is binary. I'm talking about unray. As you said - "but you do have to figure out what operand the unary operator applies to when it is between two possible operands". – Luchian Grigore Oct 18 '12 at 18:50
  • @avakar that I know, but those aren't unary. – Luchian Grigore Oct 18 '12 at 18:50
  • @LuchianGrigore, sorry, I thought you were asking about "same ambiguity as binary operators". – avakar Oct 18 '12 at 18:51
  • With unary operators, there's never any ambiguity. If a unary operator stands between two operands, there must always also be another binary operator to the left or to the right of the unary one, at which point the unary operator binds to the right or the left operand, respectively. – avakar Oct 18 '12 at 18:54
  • @LuchianGrigore Since the operators are represented with the same characters, it's really the associativity that determines the operator. For example, the compiler knows that `a++` is the valid syntax for post-increment of `a`, not some invalid syntax of a preincrement, because preincrement is right-associative and there's nothing to associate with on the right. – Seth Carnegie Oct 18 '12 at 18:55
  • @SethCarnegie that's still not a full example, and you seem to be contradicting avakar. – Luchian Grigore Oct 18 '12 at 18:59
  • @LuchianGrigore How am I contradicting him? And I didn't mean you could come up with an ambiguous expression, I mean the ambiguity is resolved by giving operators associativity. – Seth Carnegie Oct 18 '12 at 19:00
  • But you just said there's no ambiguity to talk about... :( I'm lost. – Luchian Grigore Oct 18 '12 at 19:01
  • @LuchianGrigore if we say that the post-increment is left-associative, then we know whenever we see `x--` that it's post-increment. Same goes for the other unary operators. It's that simple. You don't get the operator and then consider the associativity **like you do with binary operators**, you consider the associativity and from that derive the operator. – Seth Carnegie Oct 18 '12 at 19:04
  • @LuchianGrigore you are smarter than me so if you're confused, I probably am too but I just don't know it yet. – Seth Carnegie Oct 18 '12 at 19:06
  • @LuchianGrigore maybe someone else will have to explain or correct me if it still doesn't make sense. – Seth Carnegie Oct 18 '12 at 19:11
  • Another way to look at it is that when your recursive descent parser encounters the token sequence {`++`, `a`}, it first tries to match it as a postfix expression, fails, then tries to match it as a unary expression and succeeds. By contrast an operator-precedence parser (such as the shunting yard algorithm) does explicitly label operators with their precedence and associativity. I'm not sure how in practice you deal with ambiguous tokens like `++` that could refer to two different unary operators in an operator-precedence parser, probably like what Seth says. I'm not good at parsers. – Steve Jessop Oct 19 '12 at 11:04
  • @SteveJessop in a TDOP you also just look around and if there's something to the left or something to the right and that determines what the operator is. – Seth Carnegie Oct 19 '12 at 14:04
2

Consider the following piece of code

int *p;
*p++;

The expression *p++ can be evaluated as either (*p)++ (incrementing the object that p points to) or *(p++) (pointing to the next object pointed by p).

Because unary operators are right associative the expression *p++ will be evaluated as *(p++). (I came up with this while reading Kernighan and Ritchie.)

It seems that the precedence and associativity has been changed and postfix ++ has higher precedence than dereference * operator.

According to C11 the above expression will be evaluated as *(p++).

I hope this makes it more clear why unary operators have associativity.

Thanks to Lucian Grigore for pointing this out.

Community
  • 1
  • 1
  • Postfix is left-to-right associative - http://en.cppreference.com/w/cpp/language/operator_precedence, so the argument wouldn't hold. Plus, the order of evaluation in this case is dictated by priority, since post-increment has higher priority than dereference. – Luchian Grigore Oct 20 '16 at 20:03
  • Thanks for the insight. – Shekhar Bhandakkar Oct 21 '16 at 06:53
  • I think unary operators have no associativity at all, because same operator is not allowed to be chained, so it should be solved with precedence instead of associativity - as you told they now have understood. –  Dec 22 '19 at 07:35
1

Not sure, but if the following were valid, yes.

++i--

but as it isn't, and throws the error

lvalue required as increment operand

All behaviour of the unary operator can be explained in terms of precedence only.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Anirudh Ramanathan
  • 46,179
  • 22
  • 132
  • 191