2

What does res += (f(i), f(i + 1)), f(i + 2); evaluate to?

#include <iostream>
int f(int x)
{
    static int cnt = 0;
    return ++cnt * x;
}
int main ()
{
    int res = 0;
    for (int i = 0; i < 6; ++i) {
        res += (f(i), f(i + 1)), f(i + 2);
//        f(i);
//        res += f(i + 1);
//        f(i + 2);
        std::cout << res << std::endl;
    }
    std::cout << res;
}

I can t figure out how this line works:

res += (f(i), f(i + 1)), f(i + 2);

I understand that it is equivalent to the commented part, but why?

Hmmman
  • 47
  • 5
  • Just for your info, the code you provide is not equivalent. Anyhow, check the documentation of the "comma operator". – Ulrich Eckhardt Jan 20 '19 at 13:45
  • What about the line do you not understand? Do you know what operators are used in the expression? Do you know what operator precedence means? Do you know what parentheses do? Do you know what a function invocation looks like? – eerorika Jan 20 '19 at 13:45
  • 2
    Where did you find the code? Who wrote it? And why? – Lightness Races in Orbit Jan 20 '19 at 13:52

1 Answers1

7

This answer explains the behavior you observe with this expression:

res += (f(i), f(i + 1)), f(i + 2);

...but I would advise against using this in any production code, see the end of the answer for some reasoning.


When used in expression, the comma , corresponds to the comma operator which is a standard operator:

// Evaluate E1 then E2, discards the result of E1, return E2
E1, E2

The += operator is also an operator, with a stronger precedence than , (, has the lowest precedence in C++) so:

int f();
int g();

int x = 0;

// Equivalent to:
//   x += f(); 
//   g(); 
x += f(), g();

// Equivalent to:
//   f();
//   x += g();
x += (f(), g());

In your case, you have a pair of parenthesis wrapping the two first calls:

res += (f(i), f(i + 1)), f(i + 2);
//     ^--------------^

So:

  1. f(i), f(i + 1) is evaluated (due to the parenthesis), which result in calling f(i) then f(i + 1), and storing the result of f(i + 1) in a temporary;
  2. the temporary is added to res via the += operator;
  3. the result of res += (f(i), f(i + 1)) is discarded;
  4. f(i + 2) is evaluated.

As specified in the comment, this code is equivalent to:

f(i);
res += f(i + 1);
f(i + 2);

The compiler cannot remove the call to f(i) and f(i + 1) because these have side effects (the update of the static variable cnt).


I would advise against using the comma operator for such kind of things, mainly for clarity reasons:

  • You do not gain anything from this compared to the 3-lines versions except that the behavior of the one-liner is not that obvious (this question is a good example of this... ).
  • Due to how the comma operator works for non built-in types, the behavior was different until C++17 for non built-in types. For instance, if f was returning a big_int-like class, the order of evaluation of the operands of the operator, was not guaranteed.

Since C++17, you get fold expressions for comma operator, which is, in my opinion, one of the very few reason to use the comma operator.

Holt
  • 36,600
  • 7
  • 92
  • 139
  • Agree, with all but i feel that you should more heavily disuade the use of the comma operator in this way. It makes things harder to understand, which is an obvious problem for the OP. – Fantastic Mr Fox Jan 20 '19 at 15:33
  • @FantasticMrFox Answer edited, feel free to suggest edits if you feel this is not dissuasive enough ;) – Holt Jan 20 '19 at 17:06