Order of Evaluation of successive -> and ()

I've come across this code snippet from the chapter on the Composite design pattern from the Gang-of-Four Software Design Patterns book:

1
2
3
4
5
6
7
8
9
10
11
Currency CompositeEquipment::NetPrice() {
   Iterator<Equipment*>* i = CreateIterator(); 
   Currency total = 0;

   for (i->First(); !i->IsDone(); i->Next()) {
      total += i->CurrentItem()->NetPrice();
   }

   delete i;
   return total;
}


I understand intuitively what i->CurrentItem()->NetPrice() should evaluate to , that is, the net-price for the current item the iterator is pointing to (an Equipment object) but the order of evaluation of that statement is unclear to me.

The table of C++ operator precedence:

https://en.cppreference.com/w/cpp/language/operator_precedence

states that the function operator () has higher precedence than the member access operator -> so one would expect the evaluation order to be:

1. A = NetPrice() // Suppose NetPrice() evaluates to A
2. B = CurrentItem()
3. (i->A)->B // Suppose i->A evaluates to C
4. C->B

But the statement seems to suggest that the two membership operators are used to first get the NetPrice() member of i
Last edited on
states that the function operator () has higher precedence than the member access operator ->

I don't see where it states that. It shows both of them having the same precedence (2).

The -> operator and a function call operation () have the same precedence. The Associativity is left-to-right, so there's no ambiguity here. NetPrice() will always be called last, and furthermore, can't be evaluated until we get the return value of CurrentItem().

___________________________________

Also, I'm probably not the best person to explain this, but operator precedence != order of evaluation. The order of operation is independent from any operator precedence rules. In your case, there is no ambiguity when it comes to the order of evaluation on line 6, because the left is sequenced and therefore evaluated before the right of each term.

See the notes section of the operator precedence page you linked.
Precedence and associativity are compile-time concepts and are independent from order of evaluation, which is a runtime concept.

See: https://en.cppreference.com/w/cpp/language/eval_order

___________________________________

PS: (Unrelated to your actual question)
If any of the statements from line 3 to line 8 cause an exception, you will have a memory leak with that code. Prefer using unique_ptr (a "smart pointer").
https://en.cppreference.com/w/cpp/memory/unique_ptr

See page 18 for an example of how unique_ptrs are safer (RAII).
http://www.stroustrup.com/resource-model.pdf

Last edited on
Operator precedence and associativity tell you the order in which operators are evaluated. Associativity is just "precedence when there's a tie."

Order of evaluation tells you the order in which operands are evaluated.

In the expression total += i->CurrentItem()->NetPrice() the table tells us that -> and () have the same precedence and ties are evaluated left to right, so it's like this. I've underlined what gets evaluated at each step:
1. i->CurrentItem
2. i->CurrentItem()
3. i->CurrentItem()->NetPrice
4. i->CurrentItem()->NetPrice()

The order in which operands are evaluated is almost always unspecified! For example, in a()+b()*c() the compiler is free to call a(), b() and c() in any order it pleases. Sure it has to multiply the results of b() and c() before it adds the result of a(), but it can evaluate the operands in any order.

Two notable exceptions that we're all familiar with are the short-circuit logical && and || operators. These evaluate the left operand first, and only evaluate the right operand if they have to.
Fantastic responses, Ganado, dhayden! I see that I've misinterpreted the table. Thanks for pointing that out :)

I'm glad you folks pointed out that there's a difference between order-of-operand and order-of-operator evaluation else I would never have known.

@Ganado

The article in the link you referenced is pretty dense. I didn't understand many of the points underneath the Sequence-before "Rules" section but I'll google around for more context.

Memory leakage is something I hadn't encountered before in my shallow experience. This will require some unpacking but nonetheless thank you for bringing it up.

@dhayden

Fine points -- consistent with what Ganado and those articles had mentioned. I like your convention for showing evaluation order much more than what I had come up with :)
to add one more point to the good answers above,
ElusiveTau wrote:
this code snippet from the chapter on the Composite design pattern from the Gang-of-Four Software Design Patterns book:

The C++ code snippets in GoF range between awful and atrocious, best to forget you ever saw them. They are mostly bad translations from Java, and not anything a C++ programmer would write or even recognize as meaningful.

..and, to make a positive contribution to the thread, another way to look at the difference between precedence/associativity and evaluation order is to consider when they apply during compilation:

Given this source code:
total += i->CurrentItem()->NetPrice()
The compiler first tokenizes this line of characters, to produce this sequence:
{"total", "+=", "i", "->", "CurrentItem", "()", "->", "NetPrice", "()"}
Then it has to convert the bland stream of tokens into a tree (abstract syntax tree). This is where precedence/associativity come in: they tell the compiler how those tokens are related

  i    CurrentItem
   \  /
   [->]
     \
      [()]  NetPrice
        \  /
        [->]
          \
           [()]   total
             \   /
              [+=]
                \
                 [discard] 

(in this picture, each line from top to bottom should be read as "uses the result of".

Next, the compiler has to convert this tree to actual executable code - that's where order of evaluation comes in. in C++, order of evaluation is arbitrary: it can walk this tree depth-first, breadth-first, left-to-right, right-to-left, hop around the branches randomly, doesn't matter at all, as long all the "uses the result of" lines are faithfully reproduced in code

For example, a compiler could convert this tree to this sequence of instructions:
1. read from total
2. read from CurrentItem
3. read from i
4. execute ->
5. read from NetPrice
6. execute ()
7. execute ->
8. execute ()
9. execute +=

...technically things get more complicated with some built-in operators, including the += here, and with the above-mentioned "sequenced-before" rules, but this could serve as a mental model to explain the distinction between precedence/associativity (how the source code is read) and evaluation order (how the machine code is written)
Last edited on
Topic archived. No new replies allowed.