I came across the following piece of C++ code:

    const std::uint8_t mask = 0x06;
    std::uint8_t value = foo();

    if (mask & ~value) {
        // do something
    }

and SonarQube scan emitted a warning for this code:

    if (mask & ~value) {
        ^~~~~~~~~~~~~
        Do not apply "&" bitwise operator to a signed operand.

But, both mask and value are unsigned, so where does the signed operand come from?

Integral promotion

C++ language allows implicit conversion of integral types in some cases, this is called Integral promotion:

prvalues of small integral types (such as char) and unscoped enumeration types may be converted to prvalues of larger integral types (such as int). In particular, arithmetic operators do not accept types smaller than int as arguments, and integral promotions are automatically applied after lvalue-to-rvalue conversion, if applicable. This conversion always preserves the value.

And bitwise NOT - ~ - is an arithmetic operator, so it will involve integral promotion:

If the operand passed to a built-in arithmetic operator is integral or unscoped enumeration type, then before any other action (but after lvalue-to-rvalue conversion, if applicable), the operand undergoes integral promotion.

So, that’s what happens:

  1. value is promoted to int;
  2. bitwise ~ operator is applied to value producing int;
  3. mask is promoted to int;
  4. bitwise & operator is called with two ints;

That’s where signed operand comes from.

Interestingly, explicit cast of ~value to std::uint8_t via static_cast<std::uint8_t>(~value) silences the SonarQube warning (but does not remove the promotion).

Compiler Explorer gives the expected result:

sizeof(value)=1
sizeof(~value)=4
sizeof(mask & ~value)=4
typeid(~value)=i

So, be careful with arithmetic operators when applied to “small” integer types.