3

Symbols, like &, *, etc., are used in both expressions and declarations, which are two distinctive concepts.

In expressions, the symbols are operators, for which we have a well-defined table of precedence and associativity. When an expression is complex, we can decompose and analyze it using this table. e.g.

    (a + b) * ++c

Question: In declarations, these symbols are not operator and hence we cannot apply the table of precedence and associativity for operators. Is there a table of precedence and associativity for symbols in declarations? Or in other words, when an declaration gets complicated (try this one int*& (*&f(int*))), is there a systematic way to decompose and analyze it?

A closely related follow-up question: Some book (primer) taught us how to read complex declaration with an example of typedef:

typedef int (*tp_alias)[10]; //defines tp_alias as an pointer to an array of 10 int

Method taught by the book: use the the alias name as the starting point of reading, tp_alias is the new type name. Looking to the left, it has a *, so it is a pointer. Then look outside the parenthesis: to the right, [10] means it is an array of size 10; to the left, int means the element of the array is int.
Follow-up Question: How do we read other type aliasing declaration, such as using? Since the alias name is no longer in position? e.g. using tp_alias = int (*)[10]?
Maybe to read from within the (), but whatif there is more than one ()s?(I have not seen one but it is a possibility.)

  • Related: http://stackoverflow.com/questions/13808932/what-are-declarations-and-declarators-and-how-are-their-types-interpreted-by-the – jrok Jul 10 '14 at 20:22

2 Answers2

3

You use the spiral rule.

A good explanation of it is here.

Jashaszun
  • 9,207
  • 3
  • 29
  • 57
2

There is no precedence table for expressions!

The precedence of operators is inferred from the grammar. For example, consider the definitions of logical-and-expression and logical-or-expression:

logical-and-expression:
    inclusive-or-expression
    logical-and-expression && inclusive-or-expression

logical-or-expression:
    logical-and-expression
    logical-or-expression || logical-and-expression

Now consider the expression a || b && c. It must be parsed as a logical-or-expression (where the left side is the logical-or-expression a and the right side is the logical-and-expression b && c). It can't be parsed as a logical-and-expression, because if it were, then the left side, a || b, would have to be a logical-and-expression too, and it isn't.

On the other hand, in (a || b) && c, you can't parse it as a logical-or-expression because then you'd have (a as the left side and b) && c as the right side, and neither is a valid expression. You can parse it as a logical-and-expression because (a || b), unlike a || b, is a valid logical-and-expression.

The compiler parses the code, and builds a syntax tree. If the root node is a logical-or-expression, then the logical OR operation is done last. If the root node is a logical-and-expression, then the logical AND operation is done last. And so on.

OK, so that was the bad news. Now the good news.

Even though there's no precedence table for operators in expressions, most of the time you can just pretend there is (and of course it can be overridden by parentheses); that's how you parse expressions mentally. So, it turns out that the same is true for declarators. There is no precedence table, but you can mentally parse them as though there is.

In fact, the designers of C were wise enough to make declarations use the same "precedence rules" as expressions. This is the "declaration follows usage" rule. For example, in the expression

(*a)[10]

we have an array indexing expression containing an indirection expression, and not vice versa. In the same way, in

int (*a)[10];

what we have is an array declarator containing a pointer declarator, and not vice versa. (Therefore, the result is a pointer to array.) So if you remember from expressions that [] (array indexing) and () (function call) have higher precedence than * (indirection), you can apply that same ordering to understanding declarations too. (References are a special case; it's easy to remember that references have the same "precedence" in a declaration as pointers.)

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Very detailed explanation. But I find it hard to believe that there is no precedence for operators. I just checked the cppreference(see link below) and the C++ Primer (5th, pg 166). Both say the operator has well-defined precedences. [link: http://en.cppreference.com/w/cpp/language/operator_precedence] –  Jul 10 '14 at 21:41
  • Did you mean that there is no **evaluation order** of sub-expressions? e.g. `f()+g()+h();` does not specify which function is evaluated first? I do agree on this one. –  Jul 10 '14 at 21:44
  • @user3424826 That table is not in the standard. In the standard there are no precedences; just rules for which kinds of expressions can contain which other kinds of expressions. It does make a difference. Consider the expression `w ? x , y : z`. If the compiler parsed based on "precedence", the expression would be ill-formed since it would be parsed as `(w ? x) , (y : z)`. – Brian Bi Jul 10 '14 at 21:46
  • @user3424826 But you can *infer* a precedence based on the grammar rules in the standard. That's how people constructed those tables. – Brian Bi Jul 10 '14 at 21:47
  • Ok I tried to search over the C++ standard and did not find a table. But there are still 2 points need explanation: 1. Is there indication that these symbols has same precedence in declaration as in expression? (quote from the standard is greatly appreciated.) 2. Is it possible to have more than 1 parenthesis in type alias? e.g. something (legal) like `using tp_alias = int (*)(*)` (I know this one is illegal), that could possibly confuse the parser? –  Jul 10 '14 at 21:59
  • @modeller I don't believe that the standard says that the precedence is the same, but you can infer the precedence yourself and it's well-known that it's the same. As for multiple parentheses, does something like `int ((*a))[10]` count? You can use parentheses anywhere where it makes sense. – Brian Bi Jul 10 '14 at 22:03
  • I felt compelled to downvote you because you described the "declaration follows usage" rule as wise, instead of a horrendous mistake. – Puppy Jul 12 '14 at 11:35