I was wondering about whether the calculation of a base class offset is possible at compile time. Sure, it is fairly easy to do at runtime since the capabilities of static_cast can be leveraged and the offset is simply the difference between the base pointer to the pointer to the derived class.
My first attempt to get this at compile time looked something like the following:
struct InterfaceRoot {};
struct IInterface1 : InterfaceRoot {
virtual void MethodI1() = 0;
};
struct IInterface2 : InterfaceRoot {
virtual void MethodI2() = 0;
};
class CClass : public IInterface1, public IInterface2 {
virtual void MethodI1() override { /* do something */ }
virtual void MethodI2() override { /* do something */ }
};
int main() {
CClass instance;
constexpr int offsetI1 = 0; //first base class has no offset
constexpr int offsetI2 = sizeof(IInterface1);
//check pointer values against static_cast
IInterface1* pI1 = reinterpret_cast<IInterface1*>(reinterpret_cast<char*>(&instance) + offsetI1);
IInterface2* pI2 = reinterpret_cast<IInterface2*>(reinterpret_cast<char*>(&instance) + offsetI2);
IInterface1* pI1_static_cast = static_cast<IInterface1*>(&instance);
IInterface2* pI2_static_cast = static_cast<IInterface2*>(&instance);
return 0;
}
Here, pI1 and pI1_static_cast are equal as expected. Howerver, pI2 and pI2_static_cast are not equal!?
I can fix this by either adding a virtual function to InterfaceRoot or leaving it out alltogether. What is the reason for this?
If I setup the inheritance tree like this it works with the aforementioned method:
struct InterfaceRoot {
virtual ~InterfaceRoot() {}
};
struct IInterface1 : InterfaceRoot {
virtual void MethodI1() = 0;
};
struct IInterface2 : InterfaceRoot {
virtual void MethodI2() = 0;
};
class CClass : public IInterface1, public IInterface2 {
virtual void MethodI1() override { /* do something */ }
virtual void MethodI2() override { /* do something */ }
};
Does anybody know why this is? I am using Visual Studio 2017 by the way. Is there another way to achieve my goal at compile time, or am I better off calculating the ofsets at runtime and have a small runtime overhead?
Edit:
A working runtime implementation could look something like this:
template<typename Derived, typename Base>
inline int CalcBaseOffset() {
const static int s_off = (reinterpret_cast<char*>(static_cast<Base*>(reinterpret_cast<Derived*>(0x10000000))) - reinterpret_cast<char*>(0x10000000));
return s_off;
};
int main() {
//...
int offsetI1_RT = CalcBaseOffset<CClass, IInterface1>();
int offsetI2_RT = CalcBaseOffset<CClass, IInterface2>();
// add offsets to pointer like in the code sample above
}
This approach yields accurate results, which however come at the cost of a small runtime overhead (The runtime overhead is acceptable if there is no way to calculate the offsets at compiletime to eliminate this overhead).