Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chaining Bool values give opposite result to expected

Unthinkingly I wrote some code to check that all the values of a struct were set to 0. To accomplish this I used:

bool IsValid() {
    return !(0 == year == month == day == hour == minute == second);
}

where all struct members were of type unsigned short. I used the code as part of a larger test but noticed that it was returning false for values differing from zero, and true for values that were all equal to zero - the opposite of what I expected.

I changed the code to read:

bool IsValid() {
    return (0 != year) || (0 != month) || (0 != day) || (0 != hour) || (0 != minute) || (0 != second);
}

But would like to know what caused the odd behaviour. Is it a result of precedence? I've tried to Google this answer but found nothing, if there's any nomenclature to describe the result I'd love to know it.

I compiled the code using VS9 and VS8.

like image 337
17Twenty Avatar asked May 09 '11 15:05

17Twenty


3 Answers

== groups from left to right, so if all values are zero then:

0 == year // true
(0 == year) == month // false, since month is 0 and (0 == year) converts to 1
((0 == year) == month) == day // true

And so on.

In general, x == y == z is not equivalent to x == y && x == z as you seem to expect.

like image 110
Steve Jessop Avatar answered Nov 09 '22 07:11

Steve Jessop


The behaviour shouldn't be seen as odd. The grammar rules for == (and most but not all binary operators) specify left to right grouping so your original expression is equivalent to:

!((((((0 == year) == month) == day) == hour) == minute) == second)

Note that when compared to an integer type a bool expression with value true will promote to 1 and with value false will promote to 0. (In C the result of the equality operator is an int in any case with a value or either 1 or 0.)

This means that, for example, ((0 == year) == month) will be true if year is zero and month is one or if year is non-zero but month is zero and false otherwise.

like image 23
CB Bailey Avatar answered Nov 09 '22 08:11

CB Bailey


You have to consider how it's evaluated...

a == b == c

is asking if two of them are equal (a and b), then comparing that boolean result to the third value c! It is NOT comparing the first two values with the third. Anything beyond 2 arguments won't chain as you evidently expect.

For whatever it's worth, because C++ considers non-0 values to be "true" in a boolean context, you can express what you want simply as:

return year && month && day && hour && minute && second;

(note: your revised code says "month" twice and doesn't test minute).

Back to the chained ==s: with user-defined types and operator overloading you can create a class that compares as you expect (and it can even allow things like 0 <= x < 10 to "work" in the way it's read in mathematics), but creating something special will just confuse other programmers who already know the (weird) way these things work for builtin types in C++. Worth doing as a ten/twenty minute programming exercise though if you're keen to learn C++ in depth (hint: you need the comparison operators to return a proxy object that remembers what will be the left-hand-side value for the next comparison operator).

Finally, sometimes these "weird" boolean expressions are useful: for example, a == b == (c == d) might be phrased in English as "either (a == b) and (c == d), OR (a != b) and (c != d)", or perhaps "the equivalence of a and b is the same as the equivalence of c and d (whether true or false doesn't matter)". That might model real world situations like a double-dating scenario: if a likes/dislikes b (their date) as much as c likes/dislikes d, then they'll either hang around and have a nice time or call it quits quickly and it's painless either way... otherwise one couple will have a very tedious time of it.... Because these things can make sense, it's impossible for the compiler to know you didn't intend to create such an expression.

like image 8
Tony Delroy Avatar answered Nov 09 '22 08:11

Tony Delroy