tl;dr Is bit manipulation safe and behaving as expected when it goes through integer promotion (with types shorter than int)?
e.g.
uint8_t a, b, c;
a = b & ~c;
This is a rough MCVE of what I have:
struct X { // this is actually templated
using U = unsigned; // U is actually a dependent name and can change
U value;
};
template <bool B> auto foo(X x1, X x2) -> X
{
if (B)
return {x1.value | x2.value};
else
return {x1.value & ~x2.value};
}
This works great, but when U is changed to a integer type shorter than int, e.g. std::uint8_t then due to integer promotions I get a warning:
warning: narrowing conversion of '(int)(((unsigned char)((int)x1.X::value)) | ((unsigned char)((int)x2.X::value)))' from 'int' to 'X::U {aka unsigned char}' inside { } [-Wnarrowing]
So I added a static_cast:
struct X {
using U = std::uint8_t;
U value;
};
template <bool B> auto foo(X x1, X x2) -> X
{
if (B)
return {static_cast<X::U>(x1.value | x2.value)};
else
return {static_cast<X::U>(x1.value & ~x2.value)};
}
The question: Can the integer promotion and then the narrowing cast mess with the intended results (*)? Especially since these are casts change signedness back and forward (unsigned char -> int -> unsigned char). What about if U is signed, i.e. std::int8_t (it won't be signed in my code, but curious about the behavior if it would be).
My common sens says the code is perfectly ok, but my C++ paranoia says there is at least a chance of implementation defined behavior.
(*) is case it's not clear (or I messed up) the intended behavior is to set or clear the bits (x1 is the value, x2 is the mask, B is the set/clear op)