If `v` is a class, can `v + 1 - 1` be optimized by compiler to `v`?

Viewed 163

Consider the following code:

struct S{
    int a, b;
    S() : a(0), b(10) {}
};

S operator + (const S& v, int n){
    S r = v;
    r.a += n;
    return r;
}
S operator - (const S& v, int n){
    S r = v;
    r.b -= n;
    return r;
}

S v = S() + 1 - 1;

Is it possible that S() + 1 - 1 will be optimized to S() ?

How does a compiler even determines when this kind of things can be optimized?

2 Answers

No, a compiler is not allowed to optimize

S() + 1 - 1

into

S()

because those two expressions are not equivalent for your class S.

Even though it might be non-idiomatic, it's completely valid for S to be written as you have done, in a way that operator+ and operator- don't cancel each other out (in your case, by modifying different member variables in the 2 operators).

The compiler is required to evaluate S() + 1 before using that result in the evaluation of - 1, and is only allowed to optimize that expression if it can prove that the resulting expression would be the same. In other words, the program must follow the as-if rule. Obviously, in this case the results would be different, and so the optimization is not allowed.


By the same token, if the compiler can see that an expression, and this can be any expression at all, is equivalent to some other expression, then it is allowed to optimize it as much as it wants. But again, the compiler must adhere to the as-if rule, i.e. the resulting program must behave exactly as if none of those optimizations were ever made at all.


Note that while your implementation of S is valid according to the language rules, it is quite strange, and will surprise users of the class. I suggest only doing that for types that are very clearly domain specific, and where users of the type naturally expect that + and - are not necessarily inverses of each other.

To avoid confusion, let's write it like this:

S a;
S b = a + 1 - 1;

To better see what happens in the last line you can rewrite the operator calls explicitly:

S b = a.operator+( 1 ).operator-( 1 );

Already here we see that the compiler cannot simply skip +1 -1. To do that it would have to first prove that applying +1 and then -1 to a S is the same as not applying any operation.

Usually that is the case, since typically + and - are inverses. However, your operators are not inverses of each other, as they increment/decrement different members.

Is it possible that S() + 1 - 1 will be optimized to S() ?

No, because the result would not be the same.

How does a compiler even determines when this kind of things can be optimized?

This is covered by the so-called as-if rule. If there is no observable difference, then compilers are allowed to transform the code to get an optimization. On the other hand, an optimization may never change observable behavior (there are a few exceptions, but they don't apply here).

Related