metablaster
metablaster

Reputation: 2184

Operator precedence, inconsistent documentations

I'm refreshing my memory about operator precedence, because I try to be smart guy and avoid parentheses as much as possible, refreshing on following 2 links:

cpp reference

MS docs

One problem I have, is that those 2 "reliable" docs are not telling the same thing, I no longer know whom to trust?

For one example, Cppreference says throw keyword is in same group as the conditional operator. Microsoft's docs say the conditional operator is higher than throw. There are other differences.

Which site is correct, or are both sites wrong in different ways?

Upvotes: 0

Views: 71

Answers (1)

Nicol Bolas
Nicol Bolas

Reputation: 473447

TL;DR: The Microsoft docs can be interpreted to be less correct, depending on how you look at them.

The first thing you have to understand is that C++ as a language does not have "operator precedence" rules. C++ has a grammar; it is the grammar that defines what a particular piece of C++ syntax means. It is the C++ grammar that tells you that 5 + 3 * 4 should be considered equivalent to 5 + (3 * 4) rather than (5 + 3) * 4.

Therefore, any "operator precedence" rules that you see are merely a textual, human-readable explanation of the C++ grammar around expression parsing. As such, one can imagine that two different ways of describing the behavior of the same grammar could exist.

Consider the specific example of throw vs. the ?: operator. The Microsoft site says that ?: has higher precedence than throw, while the Cppreference site says that they have the same precedence.

First, let's look at a hypothetical C++ expression:

throw val ? one : two

By Microsoft's rules, the ?: operator has higher precedence, so would be parsed as throw (val ? one : two). By Cppreference's rules, the two operators have equal precedence. However, since they have right-to-left associativity, the ?: gets first dibs on the sub-expressions. So we have throw (val ? one : two).

So both of them resolve to the same result.

But what does the C++ grammar say? Well, here's a relevant fragment of the grammar:

throw-expression:
  throw  assignment-expression(opt)

assignment-expression:
  conditional-expression
  logical-or-expression assignment-operator initializer-clause
  throw-expression

This is parsed as a throw-expression, which contains an assignment-expression, which contains a conditional-expression, which is where our ?: lies. In short, the parser parses it as throw (val ? one : two).

So both pages are the same, and both of them are correct.

Now consider:

val ? throw one : two

How does this get parsed? Well, the thing to remember is that ?: is a ternary operator; unlike most others, it has three terms. That is, the conditional-expression itself is not finished being specified until the : <something> gets parsed.

So the precedence of throw vs ?: is irrelevant in this case. The throw one is within the ternary operator because the expression is literally within the ternary operator. The two operators are not competing.

Lastly, how about:

val ? one : throw two

Microsoft gives ?: higher precedence. By Microsoft's documentation, precedence "specifies the order of operations in expressions that contain more than one operator". So the ?: happens first.

Here's the rub though. throw by itself is actually a grammatically legal expression (it's only valid C++ within a catch clause, but the grammar is legal everywhere). As such, val ? one : throw could be a legitimate expression, which is what the Microsoft docs' rules would appear to say.

Of course, (val ? one : throw) two is not a legitimate expression, because () two isn't legal C++ grammar. So one could interpret Microsoft's rules to say that this should be a compile error.

But it's not. C++'s grammar states:

conditional-expression:
  logical-or-expression
  logical-or-expression ? expression : assignment-expression

throw two is the full assignment-expression used as the third operand of the given expression. So this should be parsed as val ? one : (throw two).

And what of Cppreference? Well, by giving them right-to-left associativity, the throw two is grouped with itself. So it should be considered val ? one : (throw two).

Upvotes: 2

Related Questions