I'm attempting to write a generic version of __builtin_clz
that handles all integer types, including signed ones. To ensure that conversion of signed to unsigned types doesn't change the bit representation, I decided to use reinterpret_cast
.
I've got stuck on int64_t
which unlike the other types doesn't seem to work with reinterpret_cast
.
I would think the code below is correct but it generates a warning in GCC.
#include <cstdint>
int countLeadingZeros(const std::int64_t value)
{
static_assert(sizeof(std::int64_t) == sizeof(unsigned long long));
return __builtin_clzll(reinterpret_cast<const unsigned long long&>(value));
}
(demo)
GCC shows a warning: dereferencing type-punned pointer will break strict-aliasing rules
.
Clang compiles it without a complaint.
Which compiler is right? If it is GCC, what is the reason for the violation of strict-aliasing?
Edit: After reading the answers, I can see that the described behavior applies not only to conversion int64_t
-> unsigned long long
but also to long
-> long long
. The latter one makes the problem a little more obvious.
If you have a signed integer type T
, you can access its value through a pointer/reference to the unsigned version of T
and vice-versa.
What you cannot do is access its value through a pointer/reference to the unsigned version of U
, where U
is not the original type. That's undefined behavior.
long
and long long
are not the same type, no matter what the size of those types say. int64_t
may be an alias for a long
, a long long
, or some other type. But unless you know that int64_t
is an alias for signed long long
(and no, testing its size is not good enough), you cannot access its value through a reference to unsigned long long
.