Thank you for being one of the few people who understands that in C/C++, `unsigned OP unsigned` can have each operand be promoted to a signed integer and then have the operation overflow and cause undefined behavior.
I chose to deal with this problem by doing a "pointless" operation to force a promotion to at least unsigned int. For example:
uint16_t x = 0xFFFF;
uint16_t y = 0xFFFF;
uint16_t z = (uint16_t)((x + 0U) * (y + 0U));
This piece of code will work on any machine, such as: (uint16_t = unsigned short = 16 bits, uint32_t = unsigned int = 32 bits); (uint16_t = unsigned short = unsigned int = 16 bits, uint32_t = unsigned long = 32 bits).
But the result is 1, whether you calculate it as 16-by-16 unsigned multiplication (you get 0xFFFE0001 truncated down to 1), or 32-by-32 signed (you multiply -1 by -1 and get 1, with no overflow).
> Integer promotion is the implicit conversion of a value of any integer type with rank less or equal to rank of int or of {a bit-field of type _Bool(until C23)/bool(since C23), int, signed int, unsigned int}, to the value of type int or unsigned int.
> If int can represent the entire range of values of the original type (or the range of values of the original bit-field), the value is converted to type int. Otherwise the value is converted to unsigned int.
The fact that the C/C++ integer conversion rules have these unintuitive footguns is why I made it a talking point.
One reason is that the language didn't originally intend for UB to be so broad. They probably expected that each value would be loaded into an int-sized register, and the calculation would then be performed on two int-sized registers with overflow handled in a platform-dependent but generally sane manner (not time travel).
Yeah, reading the rationale in the standard itself, it's is kinda obvious that they didn't consider potential UB at all: when talking about "questionable signedness" they only mention division/remainder and comparisons as being possibly affected, but not addition/multiplication (due to overflow), and comparisons behaving mostly expectedly (for some value of "expectedly") is the main selling point. But comparing signed and unsigned values without actual thought and explicit cast is almost always a bug anyway! And we had the warnings for that for, like, forever.
Makes me wonder how ergonomic would a language with actually correct signatures for the arithmetic operators be.
+: int × int → int // alternatively int × bool
-: int × int → int // alternatively int × bool
*: int × int → int
/: int × int → int
%: int × int → int
I chose to deal with this problem by doing a "pointless" operation to force a promotion to at least unsigned int. For example:
This piece of code will work on any machine, such as: (uint16_t = unsigned short = 16 bits, uint32_t = unsigned int = 32 bits); (uint16_t = unsigned short = unsigned int = 16 bits, uint32_t = unsigned long = 32 bits).