The left shift operator on signed types is tricky and will easily lead you to undefined behavior.
To put it formally, here is a quote from §6.5.7/4 Bitwise shift operators of a C11 draft, emphasis added:
The result of
E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 × 2E2 , reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 × 2E2 is representable in the result type, then that is
the resulting value; otherwise, the behavior is undefined.
char is signed on a lot of platforms (likely including on the one you're testing). Therefore the following expression can fall foul of the "non-negative" constraint in the standardese above:
n <<= 1;
n is negative, the expression has undefined behavior, even on common platforms with boring 8-bit (signed) chars.
There's a problem with the following too, on stranger implementations, related to the second constraint:
int mask = 1 << type_bits - 1;
On a platform where
sizeof(int) == sizeof(char) (possible with e.g.
CHAR_BITS==16 implementations, see What platforms have something other than 8-bit char? for examples), that shift will push the
1 into the sign bit, which has undefined behavior: 1 x 2CHAR_BITS-1 is not representable (at least in 2's complement, 1's complement or sign/magnitude representations – they stop at 2CHAR_BITS-1-1. No idea if there are other representations that fulfill the C standard's conditions and would be able to represent that).
TLDR: unsigned types are the bit-twiddler's friend.