The template function below is part of a sequence generator. Instead of manual shifts, I came up with the following union-based solution to make the operations more explicit. It works great on all the compilers tested. Godbolt link.
However despite working in practice, I am afraid there are aliasing rules that are being violated, which means it might not work in the future or in another compiler other than GCC and CLANG.
Strictly in view of the C++ standard: is the code below well formed? Does it incur in undefined behavior?
template <int BITS>
uint64_t flog2(uint64_t num) {
constexpr uint64_t MAXNUM = (uint64_t(1) << BITS);
if (num < MAXNUM) return num;
union FP {
double dbl;
struct {
uint64_t man: 52;
uint32_t exp: 11;
uint32_t sign: 1;
};
struct {
uint64_t xman: 52-BITS;
uint32_t xexp: 11+BITS;
uint32_t xsgn: 1;
};
};
FP fp;
fp.dbl = num;
fp.exp -= 1023-1+BITS;
return fp.xexp;
}
Thanks!
First of all, the program is syntactically ill-formed in ISO standard C++. Anonymous struct
members are not standard C++ (in contrast to C). They are an extension. In ISO standard C++ the struct
must be named and accessed through that name.
I'll ignore that for the rest of the answer and pretend you were accessing through such a name.
It is not an aliasing violation technically, but undefined behavior for reading an inactive member of the union object in
fp.exp -= 1023-1+BITS;
The types don't really matter for this (in contrast to aliasing). There is always only at most one active member of a union, which would the last one which was either explicitly created or written with a member access/assignment expression. In your case fp.dbl = num;
means that dbl
is the active member and the only one that may be read from.
There is one exception in the standard for accessing the common initial sequence of standard layout class type members of a union, in which case the non-active one may be accessed as if it was the active one. But even your two struct {
members have a non-empty common initial sequence only for BITS == 0
.
However, in practice compilers typically explicitly support this kind of type punning, probably already for C compatibility where it is allowed.
Of course, even setting all of this aside, the layout of bitfields and the representations of the involved types are completely implementation-defined and you can't expect this to be generally portable.