Search code examples
c++bit-manipulationstandardsc++14signed

Are the result of bitwise operations on signed integral types well-defined?


Consider this code:

using integer = int; // or any other fundamental integral type
using unsigned_integer = typename std::make_unsigned<integer>::type;
constexpr integer bits = std::numeric_limits<unsigned_integer>::digits;
integer value = -42; // or any value
integer mask = static_cast<integer>(1)<<static_cast<integer>(bits-1);
bool result_and = value & mask;
bool result_or = value | mask;
bool result_xor = value ^ mask;

I am wondering how well are these operations defined according to the standard. Do I have the guarantee to end up with the same results on all architectures? I am sure to operate on the sign bit on all architectures where this sign bit is 0 for positive numbers and 1 for negative numbers?


Solution

  • The results of bitwise and, bitwise or and bitwise xor are currently underspecified in the standard, in particular the term bitwise is never defined. We have defect report 1857: Additional questions about bits that covers this issue and says:

    The specification of the bitwise operations in 5.11 [expr.bit.and], 5.12 [expr.xor], and 5.13 [expr.or] uses the undefined term “bitwise” in describing the operations, without specifying whether it is the value or object representation that is in view.

    Part of the resolution of this might be to define “bit” (which is otherwise currently undefined in C++) as a value of a given power of 2.

    and the resolution was:

    CWG decided to reformulate the description of the operations themselves to avoid references to bits, splitting off the larger questions of defining “bit” and the like to issue 1943 for further consideration.

    Which resulted in a consolidated defect report 1943: Unspecified meaning of “bit”.

    The result of left shifting a signed type is going to depend on the underlying representation. We can see this from the defect report 1457: Undefined behavior in left-shift which made it well defined to left shift into the sign bit and says:

    The current wording of 5.8 [expr.shift] paragraph 2 makes it undefined behavior to create the most-negative integer of a given type by left-shifting a (signed) 1 into the sign bit, even though this is not uncommonly done and works correctly on the majority of (twos-complement) architectures:

    ...if E1 has a signed type and non-negative value, and E1 ⨯ 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

    As a result, this technique cannot be used in a constant expression, which will break a significant amount of code.

    Noting the emphasis on the statement works correctly on the majority of (twos-complement) architectures. So it is dependent on the underlying representation for example twos-complement.