Reputation: 797
I've read that using unions for type punning is actually undefined behavior in C++ and I was wondering how would you type pun instead?
As an example I used a union to type pun two types from two third party libraries with identical layout like this (libAQuaternion and libBQuaternion are the types of those third party libraries which I can't change):
struct libAQuaternion {
double x, y, z, w;
};
void libAFunc(libAQuaternion &p) {
p.x = p.y = p.z = p.w = 1.;
}
struct libBQuaternion {
double x, y, z, w;
};
void libBFunc(libBQuaternion &p) {
p.x = p.y = p.z = p.w = 2.;
}
union myQuat {
libAQuaternion a;
libBQuaternion b;
};
int main() {
myQuat q;
libAFunc(q.a);
libBFunc(q.b);
}
What would be the standard conforming best solution to this?
Upvotes: 2
Views: 83
Reputation: 21160
C++ doesn't allow type punning. Most of the time.
What you wrote is perfectly legal, but there is one potential hazard.
The two quaternions are standard layout classes and their common initial sequence is their entirety. It is thus legal to read the member of the other through a union
myQuat q = libAQuaternion{1, 0, 0, 0};
std::cout << q.b.x; // legal
We then note that the quaternions can only be written to by either a builtin/trivial assignment or by placement new.
Using a builtin/trivial assignment on an inactive member (or its member, recursively), causes the implicit beginning of the inactive member's lifetime.
Using a placement new will also begin the member's lifetime.
Thus upon writing to a quaternion, either its lifetime already begun, or it will begin.
Reading and writing is together called accessing, and the strict-aliasing rule forbids accessing something with another type, which is what forbids type-punning. But since we just proved that accessing either quaternions is well-defined, this is the one exception where type-punning is indeed legal.
The one hazard is when the library function writes partially to the quaternion
void someFunc(libBQuaternion& q)
{
q.x = 1;
}
myQuat q = libAQuaternion{1, 0, 0, 0};
someFunc(q.b);
std::cout << q.a.y; // UB
Unfortunately, q.a.y
is uninitialized and therefore reading it is undefined behaviour.
However, given all the previous rules and the reason behind having uninitialized variables is efficiency, it is quite unlikely compilers will take advantage of the UB and "miscompile".
Upvotes: 2
Reputation: 28892
The safest way to type punning is to use memcpy
from type A to type B. On many platforms memcpy
is an intrinsic meaning it is implemented by the compiler and therefore may well be optimized away.
Upvotes: 0
Reputation: 275650
template<class D, class S>
D bitcpy( S const* s ){
static_assert( sizeof(S)>=sizeof(D) );
D r;
memcpy( &r, s, sizeof(D) );
return r;
}
union myQuat {
libAQuaternion a;
libBQuaternion b;
void AsA(){
a = bitcpy<libAQuaternion>(&b);
}
void AsB(){
b = bitcpy<libBQuaternion>(&a);
}
};
You have to keep track of if there is an A
or a B
in the union. To switch call AsA
or AsB
; this is a noop at runtime.
I believe the union rules allow activating members via assignment; if I misunderstand the standard or the situation (these are pod types right?) things get a bit trickier. But I think that doesn't apply here.
Upvotes: 1
Reputation: 48988
What be the standard conforming best solution to this?
Write a function to convert from one quaternion to the other.
libBQuaternion convert(const libAQuaternion &Quat) {
return{Quat.x, Quat.y, Quat.z, Quat.w};
}
libAQuaternion convert(const libBQuaternion &Quat) {
return{Quat.x, Quat.y, Quat.z, Quat.w};
}
// or template if you want to
template<typename T, typename U>
T convertTo(U &&Quat) {
return{Quat.x, Quat.y, Quat.z, Quat.w};
}
Any optimizer should be able to optimize this away completely, so there should be no performance penalty.
But this will be a problem if there is a function taking one such class by lvalue ref. You would need to create a new object of the appropriate class, pass that and then reassign the correct values to the original struct. I guess you could make a function for this, but IMO the cleanest way would be to change the signature of the function to take the individual values by lvalue ref, but that is not always possible.
There is just no way of doing type punning in C++ without invoking UB.
Upvotes: 3