Therein lies the problem with signed-vs-unsigned: you can’t have it just one way or another. There was a time when I also thought to myself, “self, you should just use unsigned for everything”.
But that just doesn’t work. There will always be some point where it is needed or useful to treat what is normally useful as an unsigned value as signed, and vice versa.
The real goal, then, is to recognize
limits — recognize when an integer is operating at or near its limitations and design which behaviors to tweak it for given specific conditions (even if the condition is
other specific conditions don’t apply).
There is a very good overview and a link to a Scott Meyers article at
https://stackoverflow.com/questions/10168079/why-is-size-t-unsigned about choosing between signed and unsigned (and why C++ has unsigned types like
size_t
).
I agree with Meyers: unsigned is annoying. But it is still relevant, if for nothing more than dealing with bit patterns (where sign is not a thing) or with bytes (which in C and C++ we must access through
char
sequences).
The consequence, then, is to be pedantic when dealing with signedness: make sure to be
specific about a type’s signedness as early as possible and straight-up C-cast or static_cast your basic integer types to same-sized signed/unsigned types.
For example, in C and C++, the signedness of
char
is
implementation defined. So if you want to simply print a hexadecimal representation of a byte value, make sure to do the proper signedness casting
first, at the deepest level:
1 2
|
std::cout << std::hex << (int)( (unsigned char)'A' ) << "\n";
// ------------------
|
41 |
The tricky part about the original SO code snippet isn’t the signedness, but the fact that the casting creates a
temporary object, and the reference is bound to the temporary.
This requires us to get rid of the temporary int by another means: go through a pointer temporary:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
#include <iostream>
int main (void)
{
unsigned int u = 42;
const int& s = *(int*)(&u); // ← redirect through a pointer temporary, toss the pointer
std::cout << "u=" << u << " s=" << s << "\n";
u = 6 * 9;
std::cout << "u=" << u << " s=" << s << "\n";
}
|
By casting the signedness of the pointer temporary we achieve our goal, because there is no temporary copy of
u
created.
Messy.