First, suppose I have
int16_t a;
int8_t b;
.
I wish to do b=a;
by discarding all the MSB, including sign bit, of a
that doesn't fit in b
. What would be a proper way to do this?
I can't really find any useful, recent information on this topic. This link says that signed narrowing conversion is compiler-dependent, but it's in C not C++. I also don't want compiler-dependent behaviors. Here says that narrowing conversion should be an error. I'm pretty confused on what to do.
Second, if both a
and b
were their unsigned counterpart, then is b=a;
a proper approach? According to the first link, this is defined at least in C.
From C++20 onward, you can simply do b=a;
and get the correct behavior. The =
operator performs implicit conversion to the type of its left operand [expr.ass p3 from standard draft N4860]. Implicit conversion includes integral conversions [conv p1.2]. Integral conversion in this case results in:
the unique value of the destination type that is congruent to the source integer modulo 2^N, where N is the width of the destination type. [conv.integral p3].
Mathematically, this is exactly the effect of truncating the high bits, and compilers implement it with a simple truncation.
You may if you wish use an explicit static cast:
b = static_cast<int8_t>(a);
This may avoid warnings from some compilers; the actual effect is the same.
The note you found about narrowing conversions being forbidden is applicable only to list initialization, e.g. int8_t b{a};
. It is not relevant to simple assignment, which does narrowing conversions just fine.
Prior to C++20, integer conversion caused implementation-defined behavior if the value being converted was out of range for the destination type. As far as I know, all mainstream compilers on mainstream platforms defined the behavior to be truncation, exactly as was codified by C++20, so in practice b=a;
will do the right thing on most pre-C++20 implementations as well.
If that's not good enough for you, then you can begin by converting the value to the unsigned type uint8_t
, whose behavior for out-of-range values has always been truncation, back to at least C++98. Then do some arithmetic, which hopefully is optimized out, to ensure that you assign to b
a value that is in range:
uint8_t tmp = a;
if (tmp >= 128)
b = tmp - 256;
else
b = tmp;
Try on godbolt and notice that indeed the temporary variable is optimized out and no subtraction is done; it optimizes to a 16-bit load and an 8-bit store.