V
V
Vladimir Korshunov2022-01-13 10:57:09
C++ / C#
Vladimir Korshunov, 2022-01-13 10:57:09

In different IDEs, the code gives a different answer, how so?

#include <iostream>
#include <string>
using namespace std;

class A
{
    int *val;
public:
    A(){val = new int; *val = 0;}
    int get(){return ++(*val);}
};
int main()
{
    A a, b = a;
    cout << a.get() << b.get();
    return 0;
}

If you type this code in Xcode or onlineGDB compiler, it outputs '12', if you type it in Visual Studio, it outputs '21'. How is this even possible? Logically, '12' should be displayed.

Answer the question

In order to leave comments, you need to log in

1 answer(s)
E
Evgeny Shatunov, 2022-01-13
@GashhLab

This is the case when seemingly simple code makes you understand the many intricacies of the language.
To better understand the processes taking place in the code, you first need to carefully look at the language standard.
What does the standard tell us about operator overloading ?

A declaration whose declarator-id is an operator-function-id shall declare a function or function template or an explicit instantiation or specialization of a function template. A function so declared is an operator function.

cout << a.get() << b.get();
This code masks two calls to the same function - std::ostream& operator << ( std::ostream&, int ).
Here it is very important to clarify the form of operator overloading, because the result of the behavior of this code is highly dependent on the form of overloading used.
Regarding the standard form of overloading , operator << ()the standard says that it is an overload in the form of an external function.
So the above code can be written as:
operator<<( operator<<( cout, a.get() ), b.get() );

And it is from this moment that the most interesting begins.
What does the standard tell us about function calls? And he says very different things.
C++14 [expr.call#5.2.2.8] states that:
The evaluations of the postfix expression and of the arguments are all unsequenced relative to one another. All side effects of argument evaluations are sequenced before the function is entered (see 1.9).

C++17 [expr.call#8.2.2.5] states that:
if an operator function is invoked using operator notation, argument evaluation is sequenced as specified for the built-in operator; see 16.3.1.2.

As a result, if this code is translated as the code of the 14th (or higher) standard, this code will have the same behavior. If the code is translated as the code of the 17th (and younger) standard, its behavior will be different.
But what about the likely undefined behavior? After all, an unordered state modification is UB. And, it seems like, cout << a.get() << b.get();it can be simplified to cout << ++i << ++i;, which should more clearly show the presence of UB.
There is no UB in this code. And that's why.
To determine the order in which sections of an expression are evaluated, one should be guided by the rules for ordering expressions.
Among other rules, there are written important for us now. I will provide quotes.
2) The value computations (but not the side-effects) of the operands to any operator are sequenced before the value computation of the result of the operator (but not its side-effects).

3) When calling a function (whether or not the function is inline, and whether or not explicit function call syntax is used), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function.

5) The side effect of the built-in pre-increment and pre-decrement operators is sequenced before its value computation (implicit rule due to definition as compound assignment)

16) Every overloaded operator obeys the sequencing rules of the built-in operator it overloads when called using operator notation. (Since C++17)

19) In a shift operator expression E1<<E2and E1>>E2, every value computation and side-effect of E1 is sequenced before every value computation and side effect of E2. (Since C++17)

Prior to C++17, the order in which operands cout << a.get() << b.get();are evaluated is not defined, but the behavior of this code is defined. Therefore, when translated according to the C++14 standard, this code can produce either 12, or 21. But no 11.
Starting with C++17, the order in which operands are evaluated is strictly defined and intuitive, and the result of execution is cout << a.get() << b.get();always unambiguous. When translating this code according to the C++17 standard (and beyond), the output to the console will always and only be 12.
Prior to C++11, code behavior cout << a.get() << b.get();is undefined.
Today we no longer think about life before the C ++ 11 standard, so I will not say that in a general sense there is UB in this code. I will say that UB is not here. But nevertheless, I would recommend avoiding the presence of such code in projects, even if the C ++ 17 standard is used and beyond.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question