c++castingc++20unsignedsigned

Can I safely copy lowest bits from signed to unsigned integer by casting?


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:

  1. The value of the signed integer is within the “range of B”, i.e., it's between -2B-1 and 2B-1-1 (incl.).
  2. B is lower than or equal to the bit-width of both integral types.
  3. Implementation/architecture uses little endian.

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.


Solution

  • 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.