1

I have the following snippet of code

   #include <iostream>

   int main() {

   int64_t res;
   int some_val = 5;

   if(false)
   {
       res = static_cast<uint32_t>(some_val);
   }
   else
   {
       res = -1;
   }
   std::cout << "first check: " << res << std::endl;

   res = (false) ? static_cast<uint32_t>(some_val) : (-1);
   std::cout << "second check: " << res << std::endl;

   return 0;
}

To my surprise, the stream output is:

first check: -1

second check: 4294967295

whereas I would've expected both checks to return -1, given that they're basically the same exact expression.

I read the documentation and it says that the operator "?" should convert the 2 values to a common type, so I would've expected int64_t to be picked(which would allow for both datatypes to be represented) but this is clearly not the case and instead uint32_t is chosen, which causes the underflow when casted to -1.

Can someone explain to me what's going on here?

someCoder
  • 185
  • 3
  • 15
  • Aside, there is no such thing as "integer underflow". – n. m. could be an AI Aug 31 '23 at 08:24
  • @n.m.couldbeanAI there is, but you need to be using binary `-`, `-=` or `--`, you don't get underflow with unary `-` – Caleth Aug 31 '23 at 08:27
  • @Caleth [Underflow](https://en.wikipedia.org/wiki/Arithmetic_underflow) only exists in floating point arithmetic. Integers only have overflow, whether you add, subtract, or multiply. – n. m. could be an AI Aug 31 '23 at 08:30
  • @n.m.couldbeanAI that's not the definition I'm familiar with. I would call wraparound the grouping of overflow and underflow – Caleth Aug 31 '23 at 08:33
  • @n.m.couldbeanAI the terminology "underflow" as "overflow" on the negative end is commonly accepted. See [Wikipedia article on integer overflow](https://en.wikipedia.org/wiki/Integer_overflow). See [C++ Core Guidelines ES.104 Don't underflow](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-underflow). – Jan Schultke Aug 31 '23 at 08:37

2 Answers2

2

The common type of the expression (false) ? static_cast<uint32_t>(some_val) : (-1) is either uint32_t or unsigned int, but on many platforms those are the same type. The type you are assigning to doesn't change that.

Caleth
  • 52,200
  • 2
  • 44
  • 75
2

The conditional operator converts its operands to a common type. In your case:

res = (false) ? static_cast<uint32_t>(some_val) : (-1);

The two sides of conditional operator are of type uint32_t and int respectively. Assuming that uint32_t is a type alias for unsigned int, the -1 (of type int) is converted to unsigned int. In other words, the statement is equivalent to:

res = static_cast<int64_t>(false ? static_cast<uint32_t>(some_val)
                                 : static_cast<uint32_t>(-1));
// due to the "false ?", this is equivalent to:
res = static_cast<int64_t>(UINT32_MAX - static_cast<uint32_t>(1));

int64_t can represent all values of uint32_t, so the conversion uint32_t -> int64_t doesn't change the value.

Converting int to unsigned in such cases is decision that dates back to C (see also What happens when I mix signed and unsigned types in C++?).

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96