2

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.

zaratustra
  • 8,148
  • 8
  • 36
  • 42

4 Answers4

6

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;
}
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
2

Are you simply trying to call a function with the address of an integer that happens to live inside an A or a C object? In that case, Jeff McGlynn's answer is the way to go.

Otherwise, if you really are trying to do something tricky requiring C++'s weird pointer-to-member facility (and you almost certainly aren't):

Since classes A and C are unrelated, you will need a template function to handle both:

template <typename T>
void DoThings(int T::*x);

If C was actually derived from A, the following would work:

void DoThings(int A::*x);
j_random_hacker
  • 50,331
  • 10
  • 105
  • 169
  • Wow, I just saw this after I posted my edit. How'd we post the same thing? :-) – Jeff M Jan 07 '09 at 15:48
  • random_hacker, it's the other way around. for member pointers, the relationship is reversed: int C::*x would be the common type of the parameter – Johannes Schaub - litb Jan 07 '09 at 16:04
  • @litb: If C is derived from A, and you want to be able to call DoThings() with either a type A object or a type C object, operating on the element b in either case, the parameter will need to be of type "int A::*", right? If you used "int C::*", you couldn't pass objects of type A. – j_random_hacker Jan 07 '09 at 17:27
  • j_random_hacker, no. You don't have a A or B object/pointer. you have a A/B member pointer. Say A has "int d;", B has "float c". and you accept "float A::*". and you pass "&B::c", it would work, even though A has no member A::c !. – Johannes Schaub - litb Jan 07 '09 at 17:40
  • So, making it clear, your function would look like: void fun(base * b, int derived::*d); – Johannes Schaub - litb Jan 07 '09 at 17:45
  • Hmm, that seems surprising... Suggests you must always use pointer-to-member parameters of the most-derived type, meaning you could only ever pass objects of that or an ancestral type, never sibling types. Thanks, at some point I will read up on this properly... – j_random_hacker Jan 07 '09 at 18:02
  • ah wait. it has to be void fun(derived* b, int derived::*d); sorry. because you can't do (b->*d) because it doesn't know whether d points to a member of base. but the impossible of the implicit conversion from T d::* to T b::* stands of course – Johannes Schaub - litb Jan 07 '09 at 18:04
  • so D* converts to B* and T B::* converts to T D::* . and if you want to call the member function pointer, you have to use at least a D* . only that makes sure that you don't violate type system rules – Johannes Schaub - litb Jan 07 '09 at 18:06
  • yeah that's quite confusing :D – Johannes Schaub - litb Jan 07 '09 at 18:11
0

&A::b and &C::d are nonsensical, there is no associated address. Are you trying to get the offset of the member?

Are you sure you don't want something like the following?

DoSomething(&obj_a,&obj_a.b);
user28709
  • 59
  • 2
  • sqlrob: "&A::b" is valid C++ code, with the effect of returning a pointer-to-data-member type, of type "int (A::*)". – Aaron Jan 07 '09 at 18:16
  • I'm more used to pointer to member functions, not data. Having a pointer to member data just doesn't seem useful, especially if it has to be a pointer to a particular data member, as the syntax in the example implies. – user28709 Jan 07 '09 at 18:34
0

If you use templates as j_random_hacker suggests, and the compiler knows the type of each class at the point where you call the function, the literal answer to your question is "template <typename CLASS> void DoThings (CLASS * object, int CLASS::*MEMBER)".

Here's how it would fit into your example:

#include <iostream>

class A {
public: 
    int b;
};

class C {
public: 
    int d;
};

template <typename CLASS>
void DoThings (CLASS * object, int CLASS::*MEMBER)
{
    std::cout << object->*MEMBER << std::endl;
}

A obj_a = { 2 };
C obj_c = { 4 };

int main (int argc, const char * argv[])
{
    DoThings(&obj_a, &A::b);
    DoThings(&obj_c, &C::d);
    return 0;
}
John McFarlane
  • 5,528
  • 4
  • 34
  • 38