0

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?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
metablaster
  • 1,958
  • 12
  • 26
  • 2
    I am not a fan of "Any good programmer should know operator precedence in all cases by heart and not use parentheses." And you seem to prove my point. So my recommendation is not trying to be smart and use them whereever you are unsure now and want to have an easy life whenever you are forced to revisit your own code. – Yunnosch Jan 15 '20 at 05:23
  • https://en.cppreference.com/w/cpp/language/operator_precedence – Build Succeeded Jan 15 '20 at 05:25
  • @Mannoj You are repeating one of the links provided by OP. I do not get your point. – Yunnosch Jan 15 '20 at 05:26
  • [Elements of Programming Style - Brian Kernighan](https://www.youtube.com/watch?v=8SUkrR7ZfTA): the first rule - don't be too clever. – Evg Jan 15 '20 at 05:29
  • That link is preferred generally, Also, below I am giving the link improved the documents here on stackoverflow: https://stackoverflow.com/questions/4176328/undefined-behavior-and-sequence-points – Build Succeeded Jan 15 '20 at 05:29
  • I appreciate your opinions, btw, I did take a look into standard but found no mention of "operator precedence" using CTRL + F or looking at "contents" section. – metablaster Jan 15 '20 at 05:31
  • 2
    @metablaster: "*I don't want give any samples here from those 2 links*" Then how can we possibly know that what you're saying about them being different is true? – Nicol Bolas Jan 15 '20 at 05:32
  • 2
    From the cppreference.com link: "The standard itself doesn't specify precedence levels. They are derived from the grammar." – Some programmer dude Jan 15 '20 at 05:33
  • @NicolBolas ok, one example, cpp site says `throw` keyword is in same group as ternary operator, ms docs say ternary operator is higher than throw. that's just one example. – metablaster Jan 15 '20 at 05:36
  • @Someprogrammerdude good, maybe you (or anyone else) can explain what does that mean? – metablaster Jan 15 '20 at 05:38
  • @metablaster: It means that the concept of "precedence" is merely an outgrowth of how C++'s grammar is defined. That's why you won't find the term in the standard. The rules of precedence are just a human-readable version of the grammar. – Nicol Bolas Jan 15 '20 at 05:39
  • @NicolBolas if there is no strict and clear precedence then how is that not different from undefined behavior? I'm not trolling, just can't believe such basic concept is not well defined. – metablaster Jan 15 '20 at 05:42
  • 1
    @metablaster: Because the way an expression is parsed is what *defines* what humans think of as "precedence" and "associativity". It is well-defined; just not in a simplistic, human-readable form of "precedence" and "associativity". – Nicol Bolas Jan 15 '20 at 05:43
  • 2
    @metablaster: That is, the reason `5 + 3 * 4` is parsed as `5 + (3 * 4)` is because the grammatical term [`additive-expression` contains a `multiplicative-expression` term within it](https://timsong-cpp.github.io/cppwp/n4659/gram.expr). It is "left-to-right associative" because `additive-expression` puts the `multiplicative-expression` on the right side of the operator rather than the left. – Nicol Bolas Jan 15 '20 at 05:47
  • One example of why the standard defines operator grouping grammatically is that there is no semantic significance in putting `throw` in a separate group, as per the MS docs. Nor is there any sensible definition of associativity applied to unary operators, since associativity defines whether `a op b op c` groups as `(a op b) op c` or `a op (b op c)`, a syntax which clearly doesn't apply to unary operators. – rici Jan 15 '20 at 06:13
  • @eerorika: That sounds like an answer. Maybe you could vote to re-open (now that there's something specific about the two) so that it could be posted as one? – Nicol Bolas Jan 15 '20 at 06:20
  • @NicolBolas I've cast my vote now. I'm off to bed though, so someone else has to write the answer if this is opened. – eerorika Jan 15 '20 at 06:20
  • I agree with re-opening at this point, but I think the question would be even better if it included a concrete example of an expression that would be evaluated differently based on the two documentation sources. – JaMiT Jan 15 '20 at 06:37
  • @JaMiT: No, because then the question wouldn't exist. The question exists because the OP doesn't realize that the two different pages are saying the same thing in two different ways. They *appear* different, therefore the OP thinks they are different. – Nicol Bolas Jan 15 '20 at 06:49
  • @NicolBolas You have discovered a nefarious plot to teach people to teach themselves. Are you saying that's a bad thing? – JaMiT Jan 15 '20 at 23:24

1 Answers1

2

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).

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982