Search code examples
c++cc++03operator-precedencesequence-points

Is value of x*f(x) unspecified if f modifies x?


I've looked at a bunch of questions regarding sequence points, and haven't been able to figure out if the order of evaluation for x*f(x) is guaranteed if f modifies x, and is this different for f(x)*x.

Consider this code:

#include <iostream>

int fx(int &x) {
  x = x + 1;
  return x;
}

int f1(int &x) {
  return fx(x)*x; // Line A
}

int f2(int &x) {
  return x*fx(x); // Line B
}

int main(void) {
  int a = 6, b = 6;
  std::cout << f1(a) << " " << f2(b) << std::endl;
}

This prints 49 42 on g++ 4.8.4 (Ubuntu 14.04).

I'm wondering whether this is guaranteed behavior or unspecified.

Specifically, in this program, fx gets called twice, with x=6 both times, and returns 7 both times. The difference is that Line A computes 7*7 (taking the value of x after fx returns) while Line B computes 6*7 (taking the value of x before fx returns).

Is this guaranteed behavior? If yes, what part of the standard specifies this?

Also: If I change all the functions to use int *x instead of int &x and make corresponding changes to places they're called from, I get C code which has the same issues. Is the answer any different for C?


Solution

  • In terms of evaluation sequence, it is easier to think of x*f(x) as if it was:

    operator*(x, f(x));
    

    so that there are no mathematical preconceptions on how multiplication is supposed to work.

    As @dan04 helpfully pointed out, the standard says:

    Section 1.9.15: “Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.”

    This means that the compiler is free to evaluate these arguments in any order, the sequence point being operator* call. The only guarantee is that before the operator* is called, both arguments have to be evaluated.

    In your example, conceptually, you could be certain that at least one of the arguments will be 7, but you cannot be certain that both of them will. To me, this would be enough to label this behaviour as undefined; however, @user2079303 answer explains well why it is not technically the case.

    Regardless of whether the behaviour is undefined or indeterminate, you cannot use such an expression in a well-behaved program.