Does It Make Sense for Unary Operators to Be Associative

Does it make sense for unary operators to be associative?

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.

Unary operators and ++ or --

REWRITTEN: I guess I mis-read the spec, as pointed out by @Lundin. A better answer seems to be that the names used in this part of the standard don't make a lot of sense.

Of course both postfix and prefix increment/decrement are both just as unary as the other. That's how they're used. The fact that they have different priorities mean that they are put in different groups, and the groups have historical names which are a bit off.

Postfix/Prefix operator precedence and associativity

You can refer to the C11 standard although its section on precedence is a little hard to follow. See sec. 6.5.1. (footnote 85 says "The syntax specifies the precedence of operators in the evaluation of an expression, which is the same
as the order of the major subclauses of this subclause, highest precedence first."
)

Basically, postfix operators are higher precedence than prefix because they come earlier in that section, 6.5.2.4 vs. 6.5.3.1. So K&R is correct (no surprise there!) that *ip++ means *(ip++), which is different from (*ip)++, however its point about it being due to associativity is a bit misleading I'd say. And the geeksforgeeks site's point #2 is also correct.

Associativity of operators

There's no problem here, because all the non-primary unary operators have the same precedence as each other, and a different precedence to all binary operators. Associativity comes into effect when an operand is between two operators of the same precedence - but an operand can't be between two non-primary unary operators.

All the primary unary operators (new, typeof, default, checked, unchecked and delegate) have syntax rules which mean they're not a problem - basically, you can tell the operand by where the parenthese/braces are. Without the parentheses, there could be a problem. For example, supposed the unchecked operator didn't require parentheses. Then this:

unchecked x . y

could mean unchecked(x.y) or (unchecked x).y without more rules. It's not a problem though, as it's not valid anyway.

It's worth noting that the precedence and associativity explanations in the specification are really only informational anyway - the precise rules are encoded in the grammar of the language already. In other words, the section could be removed from the spec without affecting the validity or meaning of any program. (Good job too, as there are a couple of mistakes, IIRC...)

Associativity of function call operator in C

Here is an example, where left-right associativity of function call operator matters:

#include <stdio.h>

void foo(void)
{
puts("foo");
}

void (*bar(void))(void) // bar is a function that returns a pointer to a function
{
puts("bar");
return foo;
}

int main(void)
{
bar()();

return 0;
}

The function call:

bar()();

is equivalent to:

(bar())();

What are the ramifications of right-to-left and left-to-right associativity in C based languages?

For the most part, each operator has the associativity that makes the most sense for that operator.

All of the non-assignment binary operators have left-to-right associativity. This is useful for the obvious reason that English is read left-to-right and thus the evaluation of x + y + z is consistent with how it is read. In addition, for arithmetic operators, the semantics match what we expect from the usage of the operators in mathematics.

Assignment operators have right-to-left associativity. Left-to-right assignment would have bizarre and unexpected semantics. For example, x = y = z would result in x having the original value of y and y having the original value of z. It is expected that all three variables will have the same value after the expression is complete.

The prefix unary operators have right-to-left associativity, which makes sense because the operators closest to the operand are evaluated first, so in ~!x, !x is evaluated first, then ~ is applied to the result. It would be really, really weird were prefix operators applied with left-to-right associativity: to say that ~!x means evaluate ~x and then apply ! to the result is the complete opposite of how we think about expressions (or, at least, how most people think about expressions...).



Related Topics



Leave a reply



Submit