I want to copy the lowest (least significant) B bits from a signed integer to an unsigned integer, both of arbitrary bit-width. The following is guaranteed:
I came up with the following solution based on memcpy:
template <std::unsigned_integral U, unsigned B>
constexpr U mask() {
if constexpr (B == 8 * sizeof(U))
return std::numeric_limits<U>::max();
else
return (static_cast<U>(1) << B) - 1;
}
template <std::unsigned_integral U, unsigned B, std::signed_integral S>
U copy_bits(S s)
{
U u{};
memcpy(&u, &s, std::min(sizeof(U), sizeof(S)));
return u & mask<U, B>();
}
However, I wonder whether the solution based on simple casting will work as well:
template <std::unsigned_integral U, unsigned B, std::signed_integral S>
U copy_bits(S s) {
return static_cast<U>(s) & mask<U, B>();
}
My question is whether both variants are guaranteed to have the same defined behavior, even in cases, where the bit-width of the signed type is larger than the bit-width of the unsigned type. I only care about C++20 or later standards (two's complement signed representation).
A simple exemplary use case:
int64_t i = -8;
auto u = copy_bits<uint16_t, 4>(i);
// expected value of 'u': '0b0000000000001000'
In my live demo, both variants provide the same result, and also generate the exact same machine code.
Conversions between signed and unsigned integers are just modular arithmetic (using the cardinality of the destination type). (Prior to C++20, this was guaranteed only in the direction to an unsigned type, but that’s enough here anyway.) That easily proves that your casting implementation is correct.