5

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.

Community
  • 1
  • 1
Antonio
  • 19,451
  • 13
  • 99
  • 197
  • 1
    Read [this post](http://stackoverflow.com/questions/6770258/how-do-promotion-rules-work-when-the-signedness-on-either-side-of-a-binary-opera) and [these](http://en.cppreference.com/w/cpp/language/implicit_conversion#Integral_promotion) rules – Cory Kramer Oct 04 '16 at 16:06
  • In C, both `x,y` are converted to `int` before the `<` (Suggest 1 language tag) – chux - Reinstate Monica Oct 04 '16 at 16:11
  • @chux Sure, but based on which rule? – Antonio Oct 04 '16 at 16:14
  • @CoryKramer The rules for integral promotion say "might", not "shall". Is there something I am missing? – Antonio Oct 04 '16 at 16:15
  • C11 §6.3.1.1 2 "... If an `int` can represent all values of the original type ..., the value is converted to an `int`; otherwise, it is converted to an `unsigned int`. ..." If code used `uint16_t/int16_t`, then a difference occurs on 16-bit vs 32 `int` platforms. – chux - Reinstate Monica Oct 04 '16 at 16:17
  • @Antonio C++11 §5/9 *Relational operators* "...if either operand is unsigned, the other **shall** be converted to unsigned" – Cory Kramer Oct 04 '16 at 16:19
  • My citation is from C11. Similar specs exist in C99 and earlier. IAC `uint8_t / int8_t` is only standard since C99. – chux - Reinstate Monica Oct 04 '16 at 16:22
  • @Antonio Same comment as chux. I cited the C++11 standard but these promotion rules have existed in previous versions. – Cory Kramer Oct 04 '16 at 16:24
  • @chux 6.4.1.1.2 starts by saying "the following *may* be used", so again the obligation is missing. – Antonio Oct 05 '16 at 00:05
  • @CoryKramer I cannot find your citation in Paragraph 5.9 of C++11 ([document](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf) as suggested [here](http://stackoverflow.com/a/4653479/2436175)). Besides, the text you reported would say that the signed type would have to be converted to unsigned, which was exactly my point in the question (-128 -> 255). – Antonio Oct 05 '16 at 00:23
  • I think you mean "defined", not "deterministic". – smead Oct 05 '16 at 01:31
  • C11 §6.3.1.1 2 The first paragraph has the "The following _may_ be used...." and subsequent text of that paragraph is dependent on the "_may_". "If an `int` can represent all values..." is a second paragraph and stands on its own and is part of the larger _usual arithmetic conversions_. – chux - Reinstate Monica Oct 05 '16 at 01:52
  • @smead No, I really meant "deterministic", in the sense of (compiler's) "implementation defined". Not "undefined" in the sense that your machine can explode :) See [here](http://stackoverflow.com/questions/32132574/does-undefined-behavior-really-permit-anything-to-happen) for example. – Antonio Oct 05 '16 at 08:09

1 Answers1

8

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

  1. [...]

  2. 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)

Community
  • 1
  • 1
Antonio
  • 19,451
  • 13
  • 99
  • 197
  • 1
    Why do you keep mentioning undefined behaviour. Even if there were no integer promotion there would still not be undefined behaviour, just "wrong result" as you put it – M.M Oct 05 '16 at 01:34
  • Re. the last paragraph, " The purpose is to yield a common type, which is also the type of the result." refers to the previous sentence "Many binary operators" -- note "Many", not "All" – M.M Oct 05 '16 at 01:36
  • 1
    "Relational operators return a boolean value" is where C/C++ part company. In C, they return an `int`. – chux - Reinstate Monica Oct 05 '16 at 01:55
  • @M.M In the answer I had used "undefined" in place of "undeterministic" (implementation defined), corrected it now. If the phrase says "Many binary operators [...] cause conversions [...]". If something cause conversions, like the relational operators, than all that follows in the paragraph applies, including "which is also the type of the result". Also seen the other way around: relational operators require (as many other binary operators) the usual arithmetic conversions, which say that the type it converts to is also the type of the result. The C++11 standard is definitely in contradiction. – Antonio Oct 05 '16 at 08:05
  • The _contradiction_ your describe is misplaced. The result of the _arithmetic conversion_ is 2 values of the common type. The common type version of both arguments are then applied to the relationship operator which results in `int/bool` (C/C++). – chux - Reinstate Monica Oct 05 '16 at 13:43
  • @chux No. Just before it says "***Many binary operators*** *that expect operands of arithmetic or enumeration type cause conversions and* ***yield result types*** *in a similar way.*" So the phrase after is clearly referring to the result type of the binary operator. – Antonio Oct 05 '16 at 14:47
  • There is no contradiction as that says "Many binary operators" and not "All binary operators" so it need not apply to relational operators. – chux - Reinstate Monica Oct 05 '16 at 14:56
  • @chux I have already answered this point raised by M.M. [above](http://stackoverflow.com/questions/39856970/comparing-8-bits-types-of-different-signedness-int8-t-uint8-t-is-result-dete/39863945?noredirect=1#comment67024254_39863945). – Antonio Oct 05 '16 at 14:59