2

Given a struct Derived: Base, Other, is it legal to cast T Other::* -> T Derived::* -> T Base::* and dereference it on a Base* that points to a Derived?

I know that the two casts required to do this are valid individually: I can go from a T Other::* to a T Derived::* with an implicit conversion. And it's also allowed to go from a T Derived::* to a T Base::* as long as the resulting member pointer gets dereferenced on an actual Derived (even if it's through a Base*). What I'm not sure about is whether it's allowed to combine these two casts to cast between member pointer types of unrelated base classes.

Here's some example code (should return 2):

struct Base {
    int m_baseInt = 1;
};

struct Other {
    int m_int = 2;
};

struct Derived: Base, Other {};

int main() {
    Derived d;
    int Derived::* derivedMemPtr = &Other::m_int; // Legal, refers to m_int within Derived's Other subobject
    int Base::* baseMemPtr = static_cast<int Base::*>(derivedMemPtr); // Also legal on its own

    Base *basePtr = &d; // Obviously legal
    return basePtr->*baseMemPtr; // Legal? Is this guaranteed to refer to Derived::Other::m_int?
}

Background:

I've got a non-templated base class that performs complex initialization of member variables in derived classes. These members can't be initialized independently. Derived classes pass pointers to their members to the base class:

Base::addMemberToInitialize(static_cast<Type Base::*>(&Derived::m_whatever), other_info);

I'd now like to expand this to members of other bases of the Derived class so that I can break Derived up into functionally independent pieces and re-use them. Essentially, I'd like to do something like this:

struct Base {
protected:
  void addMemberToInitialize(Type Base::*, Info);
  void doTheComplexInitialization(Stuff);
  /* Protected constructors/destructor/operators */
};
struct Derived final: Base, Other, Another, ... {
  Derived() {
    addMemberToInitialize(
      static_cast<Type Base::*>(static_cast<Type Derived::*>(&Other::m_whatever)),
      other_info);
    /* ... */
    doTheComplexInitialization(some_stuff);
  }
}
Jonathan S.
  • 1,796
  • 5
  • 14
  • Seems legal. https://eel.is/c++draft/expr.static.cast#12 – n. m. could be an AI Apr 05 '22 at 18:39
  • @n.1.8e9-where's-my-sharem. Thanks! In that case, I think I've just found a *very* ugly bug in GCC's ABI, though... https://godbolt.org/z/YGosr744E (It represents nullptr as -1 for a pointer-to-member) – Jonathan S. Apr 05 '22 at 18:47
  • *It represents nullptr as -1 for a pointer-to-member* What's wrong with that? – n. m. could be an AI Apr 05 '22 at 18:49
  • The problem is that, given a ```struct Derived: Base1, Base2```, if you cast ```Base1::* -> Derived::* -> Base2::*```, the member pointer becomes negative (since it points to memory *before* Base2) - and therefore potentially -1. The godbolt link shows how GCC confuses such a non-null member pointer with nullptr. If these casts are indeed legal, then GCC's ABI is broken in a very, very ugly way. – Jonathan S. Apr 05 '22 at 18:51
  • Ah, that makes more sense. Thanks a lot! Feel free to close this question as a dupe then. I didn't find this earlier question, unfortunately. – Jonathan S. Apr 05 '22 at 19:15

0 Answers0