I use unsigned ints to make my code and its intent more clear. One thing I do to guard against unexpected implicit conversions when doing arithmetic with both signed and unsigned types is to use an unsigned short (2 bytes usually) for my unsigned variables. This is effective for a couple reasons:
- When you do arithmetic with your unsigned short variables and literals (which are of type int) or variables of type int, this ensures the unsigned variable will always be promoted to an int before evaluating the expression, since int always has a higher rank than short. This avoids any unexpected behavior doing arithmetic with signed and unsigned types, assuming the result of the expression fits into a signed int of course.
- Most of the time, the unsigned variables you're using won't exceed the max value of an unsigned 2-byte short (65,535)
The general principle is that the type of your unsigned variables should have a lower rank than the type of the signed variables in order to ensure promotion to the signed type. Then you won't have any unexpected overflow behavior. Obviously you can't ensure this all the time, but (most) often it's feasible to ensure this.
For example, recently I had some for loop something like this:
const unsigned short cuint = 5;
for(unsigned short i=0; i<10; ++i)
if((i-2)%cuint == 0)
The literal '2' is of type int. If i was an unsigned int instead of an unsigned short, then in the sub-expression (i-2), 2 would be promoted to an unsigned int (since unsigned int has a higher priority than signed int). If i = 0, then the sub-expression equals (0u-2u) = some massive value due to overflow. Same idea with i = 1. However, since i is an unsigned short, it gets promoted to the same type as literal '2', which is signed int, and everything works fine.
For added safety: in the rare case where the architecture you're implementing on causes int to be 2 bytes, this might cause both operands in the arithmetic expression to be promoted to unsigned int in the case where the unsigned short variable doesn't fit into the signed 2-byte int, the latter of which has a max value of 32,767 < 65,535. (See https://stackoverflow.com/questions/17832815/c-implicit-conversion-signed-unsigned for more details). To guard against this you can simply add a static_assert to your program as follows:
static_assert(sizeof(int) == 4, "int must be 4 bytes");
and it won't compile on architectures where int is 2 bytes.