This is related to the question about type erasure here: boost te memory access failure with visual c++. Feel free to edit the title if you think you can phrase this a better way.
In the type erasure library, a trick is performed which is essentially akin to the following:
struct implementer {
void func(){};
};
struct base {
virtual void f() const = 0;
};
struct poly : public implementer, public base {
void f() const {}
};
template <typename T>
void call(const T& v) {
const auto& b= reinterpret_cast<const base &>(v);
b.f();
}
int main() {
poly p;
implementer &i = p;
call(i);
}
The key here being that because you are passing the implementer to the call function, reinterperet_cast must be used to get the poly part out and do the retrieval of the erased function. This compiles and runs (correctly getting the base vtable) in gcc and clang.
https://godbolt.org/z/os9W71av6
It also compiles in MSVC, but when I run it, the reinterperet_cast breaks the vtable for the value b leading to a read access violation:
Exception thrown: read access violation. b was 0xFFFFFFFFFFFFFFFF.
So the question then is is this type of cast legal in c++, or is it undefined behaviour?
Why this seems to "work" on GCC/Clang on Linux:
implementeris subject to empty base class optimization and is placed in the first zero bytes ofpoly. The next 8 bytes store the vtable pointer and all zero members ofbase.This means that
p,static_cast<implementer&>(p)(i) andstatic_cast<base&>(p)all have the same address.You still have UB because the
reinterpret_castdoesn't point to abaseobject, but that can be fixed by astd::launder:Why this doesn't work on MSVC/Clang with the Microsoft ABI:
Since
polyisn't standard layout, the members are allowed to be rearranged. Crucially,implementerbeing stored in zero bytes at the beginning is not guaranteed. The Microsoft ABI stores 8 bytes for the vtable pointer /baseand then zero bytes forbase.This means
&i != &p(it is 8 bytes greater,&i == reinterpret_cast<char*>(p) + sizeof(void*)).So that means that your
reinterpret_castis at the wrong address. You would have to adjust the pointer again on Windows:The amount you need to adjust would obviously be different depending on the exact layout of the class.
What you are doing is essentially a sidecast. If you make
implementervirtual you can do this with adynamic_cast: