0

I don't understand why my two expressions produce the same result even though the second expression calls f() twice.

I am using gcc with C++20 enabled.

#include <iostream>

using namespace std;

int& f(int& i, string name);

int main() {
    puts("\n-------------------------------------\n");

    int x = 5;
    /* expression one */
    printf("the result of (f(x) += 1) is:--> %d\n", f(x, "one") += 1);
    printf("x is: %d\n",x);
    
    printf("********************\n");

    x = 5;
    /* expression two */
    printf("the result is:--> %d\n", f(x, "two") = f(x, "three") + 1);
    printf("x is: %d\n", x);

    puts("\n-------------------------------------\n");
    return EXIT_SUCCESS;
}

int& f(int& i, string name) {
    ++i;
    printf("<%s> value of i is: %d\n", name.c_str(), i);
    return i;
}

The output of my program is:

-------------------------------------

<one> value of i is: 6
the result of (f(x) += 1) is:--> 7
x is: 7
********************
<three> value of i is: 6
<two> value of i is: 7
the result is:--> 7
x is: 7

-------------------------------------
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • Don't know about C++ but in C you cannot do `f(x, "one") += 1` – Weather Vane Oct 07 '22 at 10:56
  • Have you tried stepping through the code with a debugger? Why do you expect different results? 7 seems correct in both cases to me – Quimby Oct 07 '22 at 10:56
  • Why would you even want to do something like this? – Mestkon Oct 07 '22 at 10:57
  • 2
    @WeatherVane that's valid C++ since `f` is returning a reference – perivesta Oct 07 '22 at 10:58
  • In C there is no `int& f()` . – Gerhardh Oct 07 '22 at 10:58
  • You should decide which language you are learning. C/C++ is not a language but 2 very different language with some common subset. – Gerhardh Oct 07 '22 at 10:59
  • Since you are coding in C++, please note that `stdio.h` is perhaps the worst library ever made for any programming language, all categories. So you should be using iostream instead; not just because you are coding in C++ but because eradicating `stdio.h` from your code is a bliss. – Lundin Oct 07 '22 at 11:00
  • 2
    In order to explain you why it's different than you thought it should be, we need to know what you thought the result would be. I think the output and result is correct. – Thomas Weller Oct 07 '22 at 11:00
  • @Quimby : as a matter of fact, no, I logged the steps in code, I don't think so to need debugging – Hinata Chikao Oct 07 '22 at 11:01
  • Please, in production code, do not write code like math. In math you can say f(x+1)=f(x)+1 or something like that. This does not translate into code 1:1. Math and code are two different languages. – Thomas Weller Oct 07 '22 at 11:03
  • 2
    Why would you not expect the same results for both expression? What results do you expect, and can you explain why, exactly, you expect different results? P.S. C and C++ are completely different languages? Which textbook are you using that attempts to teach both languages at the same time, like this? I've never heard of a textbook like that. – Sam Varshavchik Oct 07 '22 at 11:06
  • @HinataChikao Well, in that case as other have said, you should say why you think the result is incorrect, or what did you expect? – Quimby Oct 07 '22 at 11:10
  • @ThomasWeller: Unlike the first statement, In second expression function f() is called twice. – Hinata Chikao Oct 07 '22 at 11:12
  • @SamVarshavchik: Unlike the first statement, In second expression function f() is called twice – Hinata Chikao Oct 07 '22 at 11:12
  • @Quimby: Unlike the first statement, In second expression function f() is called twice – Hinata Chikao Oct 07 '22 at 11:13
  • Possible duplicate: https://stackoverflow.com/a/8295226/1387438 so you are relaying on unspecified behavior! – Marek R Oct 07 '22 at 11:20
  • In C++20, the right call to f() happens first, then the expression is evaluated, whose result is 7. Then the f() on the left is invoked, but after it returns, the value of the referenced `x` is overwritten by the result of the right expression (7). The final result of the assignment is then the lvalue that has been assgned to; its value is now 7. – j6t Oct 07 '22 at 11:20
  • @MarekR AFAICS, in C++20, no unspecified evaluations happen in this case. – j6t Oct 07 '22 at 11:22
  • @j62 you are right, there is: [The right operand is sequenced before the left operand.](http://eel.is/c++draft/expr.ass#1) – Marek R Oct 07 '22 at 11:33
  • @WeatherVane It is similar like doing `*(f(x, "one")) += 1`, for a function `f` returning an `int *` pointer, which is valid C. – Sebastian Oct 07 '22 at 12:11

2 Answers2

3

If we break expression 2 into separate steps it might become clearer what is happening:

f(x, "two") = f(x, "three") + 1;

Is equivalent to (though note the order of evaluation isn't guaranteed before c++17):

int b = f(x, "three") + 1;
int& a = f(x, "two");
a = b;

However as f(x) returns a reference to x, a is also a reference to x. Using this if we then expand the function calls we get:

int b = ++x + 1;
++x;
x = b;

Notice how the second ++x is ignored, this is why it doesn't have the value you expect.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
1

Note that this is undefined behaviour before (and correct after) C++17 which explicitly sequences evaluation of left, right sides including their side-effects.

f(x, "one") += 1

is evaluated as:

  1. Evaluate right side to 1.
  2. Evaluate left side:
    1. Set i: 5->6
    2. Return ref to i which holds 6.
  3. Evaluate += by adding one to the returned reference, setting i: 6->7.

The expression is evaluated to ref to i holding 7.

f(x, "two") = f(x, "three") + 1
  1. Evaluate right side:
    1. Set i: 5->6
    2. Return ref to i which holds 6 now.
    3. Add 1 -> right side is 7.
  2. Evaluate left side:
    1. Set i: 6->7.
    2. Return ref to i which holds 7 now.
  3. Evaluate = by assigning the right side(7) to left side(i), setting i to 7.

The expression is evaluated to ref to i holding 7.

In the second expression it doesn't matter what happens to i inside left f call - if it returns i ref, it will be set to 7.

Quimby
  • 17,735
  • 4
  • 35
  • 55
  • 1
    I appreciate you for your response. Thank you so much. Your answer was great. – Hinata Chikao Oct 07 '22 at 11:39
  • Do you happen to have a specification reference for the UB claims? Always looking to have a strong source for that. Thank you. – Thomas Weller Oct 07 '22 at 11:41
  • 2
    @ThomasWeller https://en.cppreference.com/w/cpp/language/eval_order `If a side effect on a memory location is unsequenced relative to another side effect on the same memory location, the behavior is undefined. ` – Alan Birtles Oct 07 '22 at 11:44
  • 1
    @ThomasWeller AFAIK the Standard does not call left,right operands of `=` in indeterminately sequenced as in other places but it does not sequence them either. Compare [c++14 standard for `=`](https://timsong-cpp.github.io/cppwp/n4140/expr#ass-1) to [c++17](http://eel.is/c++draft/expr.compound#expr.ass-1) which adds "The right operand is sequenced before the left operand." – Quimby Oct 07 '22 at 12:02
  • 1
    But the call to `f` add some sequences. Memory's change inside the function doesn't introduce UB with change from outside. `f() = f()++` would be problematic though. – Jarod42 Oct 07 '22 at 12:14
  • @Jarod42 Yes, two `f` are sequenced and cannot interleave so the `i++` are safe. The issue is if they are both called/evaluated before the `+1`, you get 8 as a result, that can happen before C++17. But that might be implementation-defined instead of undefined, not sure. – Quimby Oct 07 '22 at 15:41