Unary operators don't have associativity, really. At least, not as individual operators. As you say, it's impossible for a unary operator to compete with another instance of itself for the same operand; that can only happen with a binary operator.
On the other hand, the grammatical definition of associativity doesn't just apply between two identical operators. It applies between two operators of the same class. For example, +
and -
associate mutually, since they both have the same precedence.
Postfix operators including, for example, array subscription and function application) normally associate more tightly than any other operator, including prefix operators. We could annotate that by putting postfix operators in a higher precedence level, or we could annotate it by putting prefix and postfix operators in the same precedence level, and then saying that that precedence level associates to the right.
Both of those descriptions would explain why *f()
means "dereference the result of calling f
" —*(f())
— and not "call the function referenced by what f
points at" —(*f)()
.
Personally, I find it more intuitive to put postfix operators in a different precedence level than prefix operators, and prefix operators in their own precedence levels as well. But the original precedence declaration syntax for yacc did not provide a declaration for precedence levels without associativity. (%nonassoc
is different; it prevents chaining, which actually modifies the grammar.) So you had to declare unary operators as %left
or %right
. Of those two choices, %right
makes more sense, because postfix operators (usually) associate more strongly than prefix operators. So that's the usual style. But it's really not very clear.
Bison does allow you to declare a precedence level without associativity, using the %precedence
declaration. That's handy for unary operators. But many other yacc-based parser generators do not provide this feature, so you'll still often see unary operators declared with %right
.