C++ Notes: Function Call Order

Here is an example that illustrates some of the issues with function calls.
  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
// functions/call-order.cpp -- Illustrates order of calls.
// Fred Swartz 2003-09-02

#include <iostream>
using namespace std;
//--------------------------------- prototypes
int even(int a);
int odd(int b);
int ulam(int c);

//--------------------------------- main
int main() {
    // What does each statement print?
    cout << odd(4)              << endl;
    cout << (even(2) + odd(3))  << endl; // (Note 1)
    cout << ulam(ulam(ulam(5))) << endl; // (Note 2)
}
 

//---------------------------------- even
int even(int a) {
    cout << "even(" << a << ")" << endl;
    return a/2;
}

//---------------------------------- odd
int odd(int b) {
    cout << "odd(" << b << ")" << endl;
    return 3*b + 1;
}

//---------------------------------- ulam
int ulam(int c) {  
    cout << "ulam(" << c << ")" << endl;
    if (c%2 == 0) {
       return even(c);
    }else{
       return odd(c);
    }
}
The text from the above example can be selected, copied, and pasted into an editor.

Notes

  1. Order Ambiguity. The order of evaluation of operands is not defined in C++ (unlike Java where it's strictly left to right). It's possible for the rightmost call to be made before the leftmost call. Here are two possible orders of execution.
    // Original: cout << (even(2) + odd(3)) << endl;
    int t1 = even(2); // left before right operand eval.
    int t2 = odd(3);
    cout << t1+t2 << endl;
    or
    // Original: cout << (even(2) + odd(3)) << endl;
    int t2 = odd(3); // right before left operand eval.
    int t1 = even(2);
    cout << t1+t2 << endl;
    I've see it done both ways in various compilers, so this is a real portability issue. However, the order is only important if both functions have interacting side-effects (I/O, change globals, ...).
  2. Use intermediate variables for clarity. If it's difficult to understand an expression, compute it in pieces. The compiler does this internally. One of the above examples can be rewritten.
    //Original: cout << ulam(ulam(ulam(5))) << endl << endl;
    int t1 = ulam(5);   // the inner call must be made first.
    int t2 = ulam(t1);  // that result is used to make the next call.
    int t3 = ulam(t2);  // only now can we make the outer function call.
    cout << t3 << endl << endl;