This is because on x64, the instruction SHL
(when operating on a 64-bit source/destination operand) only uses the bottom 6 bits of the shift amount. In effect, you are shifting by 0 bits.
From the "Intel 64 and IA-32 Architecture Software Developer's Manual" (can be downloaded from Intel in PDF form, which is hard to link into,) under the entry for "SAL/SAR/SHL/SHR - Shift" instructions:
The count is masked to 5 bits (or 6 bits if in 64-bit mode and REX.W is used). The count range is limited to 0 to 31 (or 63 if 64-bit mode and REX.W is used).
As commented below, it is also an "Undefined Behavior" in the C++ language to shift an integer by more bits than its size. (Thanks to @sgarizvi for the reference.) The C++ standard, under Section 8.5.7 (Shift Operators) states that:
The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand...
That's why the compiler is producing code that gives different results under different conditions (constant or variable shift count, optimized or not, etc.)
About how to "fix" it, I have no clever tricks. You can do something like this:
template <typename IntT>
IntT ShiftLeft (IntT x, unsigned count) {
// Optional check; depends on how much you want to embrace C++!
static_assert(std::is_integral_v<IntT>, "This shift only works for integral types.");
if (count < sizeof(IntT) * 8)
return x << count;
else
return 0;
}
This code would work for signed and unsigned integer types (left-shift is the same for signed and unsigned values.)