Search code examples
c++classpointersmember-pointers

How to cast member variable pointer to generic type in C++


I have code similar to this in my application:

class A
{
  public: int b;
}

class C
{
  public: int d;
}

void DoThings (void *arg1, MYSTERYTYPE arg2);

A obj_a;
C obj_c;

DoThings(&obj_a, &A::b);
DoThings(&obj_c, &C::d);

The question is - What should MYSTERYTYPE be? neither void* nor int work, despite the value &A::b being printed just fine if you output it through a printf.

Clarifications: Yes, &A::b is defined under C++. Yes, I am trying to get the offset to a class member. Yes, I am being tricky.

Edit: Oh I can use offsetof(). Thanks anyway.


Solution

  • You have a data member pointer to two unrelated classes. Well, you can't find a common type that can hold both pointers. It will only work if the function parameter is a data member pointer to a member of the derived, because it's guaranteed to contain the member too, if a base contains it:

    struct a { int c; }; struct b : a { }; int main() { int b::*d = &a::c; }
    

    Update: I think i should write why the above converts from a::* to b::* implicitly. After all, we usually have b* to a* ! Consider:

    struct a { };
    struct b : a { int c; };
    struct e : a { };
    int main() { int a::*d = &b::c; e e_; (e_.*d) = 10; /* oops! */ }
    

    If the above would be valid, you would really much screw up. The above is not valid, because conversion from b::* to a::* is not implicit. As you see, we assigned a pointer to b::c, and then we could dereference it using a class that doesn't contain it at all! (e). The compiler enforces this order:

    int main() { int b::*d = &b::c; e e_; (e_.*d) = 10; /* bug! */ }
    

    It fails to compile now, because e is not derived from b, the class the member pointer pointer belongs to. Good! The following, however, is very valid and compiles, of course (changed classes a and b):

    struct a { int c; };
    struct b : a { };
    struct e : a { };
    int main() { int e::*d = &a::c; e e_; (e_.*d) = 10; /* works! */ }
    

    To make it work for your case, you have to make your function a template:

    template<typename Class>
    void DoThings (int Class::*arg) { /* do something with arg... */ }
    

    Now, the compiler will auto-deduce the right class that the given member pointer belongs too. You will have to pass the instance alongside of the member pointer to actually make use of it:

    template<typename Class>
    void DoThings (Class & t, int Class::*arg) { 
        /* do something with arg... */ 
        (t.*arg) = 10;
    }
    

    If you just want to set some member you already know at the time you write DoThings, the following suffices:

    template<typename Class>
    void DoThings (Class & t) {  
        t.c = 10;
    }