Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is one of these examples undefined behavior and the other is not?

A comment to this reply states that the following code should not be used because it exhibits undefined behavior:

int old = (std::cin >> old, old);

A similar code was also greatly scorned here, in particular for exhibiting undefined behavior.

On the other hand, this highly up-voted reply suggests the following code as an example of usefulness of the comma operator:

while (cin >> str, str != "STOP") {
    //process str
}

I assume that this code would not be up-voted if it exhibited undefined behavior.

The question: if the first code is undefined behavior (presumably because of using the result of reading from cin without checking the status of the latter), then why is the second code fine?

EDIT: The question is answered in the comment section for the first example. What the second example does not show is that str is an instance of std::string and so is initialized. Therefore, there is no undefined behavior.

like image 760
AlwaysLearning Avatar asked Mar 03 '23 11:03

AlwaysLearning


2 Answers

The statement int old = (std::cin >> old, old); is well-defined since C++11 assuming that non-whitespace characters are read. This is because old is set to zero if std::cin fails in such a case. The use of the expression separator operator , is also legitimate as it sequences the expressions std::cin >> old and old. If no non-whitespace characters are countered then old is still not changed by std::cin >> old and the behaviour of the code is undefined.

Assuming str is a std::string type, (cin >> str, str != "STOP") is always well-defined. If cin >> str fails then initial (perhaps default-constructed) value of str is retained, and again , sequences the expressions.

like image 138
Bathsheba Avatar answered May 11 '23 03:05

Bathsheba


Both answers are bad answers but only first one may exhibit undefined behavior.

int old = (std::cin >> old, old);

When std::cin >> old fails then old can have its value that it had. So it can remain as uninitialized int and so reading from it is undefined behavior.

while (cin >> str, str != "STOP") {
    //process str
}

When cin >> str fails then str has its previous value. Assuming str is std::string it can not have had uninitialized state unlike aggregate types. So it will have its previous value for example default-constructed value "" or previously read value "MOP". Reading from it and comparing with "STOP" is never undefined behavior. However it is very likely that cin >> str will fail next cycle again and so str will never be "STOP" and so it will be endless loop and so && is likely better than comma there.

Also it is worth to note that we can get real crash when we try to combine first example and second example:

std::string old = (std::cin >> old, old);

When streams are not set to throw on failures then we should check state of those manually (and std::cin is not special) since those may end and fail. Unfortunately lot of tutorial examples do not do it. Therefore some people seem to think that the stream operations are suitable for inserting into middle of some expression using comma operator.

Edit:

I am unsure about comment that it is different from C++11. Did dig, but couldn't find confirmation in standard. Trying this code with g++ did not read zero on case of error:

#include <iostream>
#include <sstream>

int main()
{
    std::stringstream in("42");

    int i = 0;
    std::cout << i << '\n'; // outputs 0
    in >> i;
    std::cout << i << '\n'; // outputs 42
    i = 666;
    in >> i;
    std::cout << i << '\n'; // outputs 666 
    // so it did leave value of i like it was 
}

Demo. Also std::cin does not seem special in any way. Of course it may be that the implementation of g++ is incorrect or that the failed input is required to set the i only on case of subset of failures and so the first example still may exhibit undefined behavior.

like image 38
Öö Tiib Avatar answered May 11 '23 01:05

Öö Tiib