I run this simple program:
#include <stdint.h>
#include <iostream>
int main() {
uint8_t x = 100;
int8_t y = -128;
if (x < y) {
std::cout << (int) x << " is less than " << (int) y << std::endl;
} else {
std::cout << (int) y << " is less than " << (int) x << std::endl;
}
}
With output, correctly:
-128 is less than 100
I was at first surprised to see no signedness warning was generated.
I was then surprised not to have a wrong conversion going on (-128 -> 255) and therefore not getting a wrong result.
Then I read this:
1 A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int. [§ 4.5]
Link to standard n2356
What does it mean "Can be converted"? Is it up to the compiler implementation if this conversion will happen and therefore if this expression will return a correct value?
The point is that the compiler shall search for a common type to which convert the 2 operands, but I don't find any obligation in the standard to do its best so that this common type is able to represent all possible values of both the 2 input types.
Note: I tagged also C as this case seems to be also applicable to it.
Related question: Comparison signed and unsigned char. Also this.
Yes, the result is deterministic, not (compiler's) implementation defined. Here follows the motivation for C++11 (it should be possible to do the same for other), following the document here (link suggested here)
It's necessary to combine all of the following:
5.9 Relational operators
[...]
The usual arithmetic conversions are performed on operands of arithmetic or enumeration type.
To find the usual arithmetic conversion we need to go to the incipit of Chapter 5, paragraph 9:
Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:
- [...] (Enumeration and floating point types)
Otherwise, the integral promotions (4.5) shall be performed on both operands.[59] Then the following rules shall be applied to the promoted operands:
- If both operands have the same type, no further conversion is needed.
- [...]
So, integral promotion, citing from 4.5:
A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
So:
We have a relational operator, the usual arithmetic conversions will be used. These oblige to apply integral promotion. An integral promotion for uint8_t
and int8_t
is possible to int
, so it is obligatorily applied.
Therefore the comparison between a uint8_t
and int8_t
is transformed by the compiler into a comparison between 2 int
. There is no undeterministic behaviour.
There was a similar Q/A here (about short
type though), which led me to the right path.
Note the following contradiction though: Relational operators return a boolean value (5.9.1), yet they use the usual arithmetic conversions which is used to obtain 2 operands of the same type. But, here lays the problem, the definition of usual arithmetic conversion says that the common type will be also the type of the result, which isn't the case for relational operators!!
The contradiction is not there for C11, where the result returned by relational operators is indeed an int
. (Thanks chux)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With