The answer by Vlad is the explanation.
Here is a visualisation of how hard it is to get something into a special data type, in the presence of promotion:
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
unsigned char c = 64;
if (c << 2 ) {printf("Yes 1.\n");}
if (c << '\2') {printf("Yes 2.\n");}
if ((unsigned char)(c << 2)) {printf("Yes 3.\n");} else {printf(" No 3.\n");}
if ((unsigned char)(c) << (unsigned char)(2)) {printf("Yes 4.\n");} else {printf(" No 4.\n");}
return 0;
}
Output (e.g. here https://www.tutorialspoint.com/compile_c_online.php ):
Hello, World!
Yes 1.
Yes 2.
No 3.
Yes 4.
The version from your code fails in 1.
A version attempting to use unsigned char operands fails in 2.
Only 3, an explicit cast after the operation manages to override the promotion.
While 4, attempting to use explicit cast on the operands before the operation fails again.