15

I'm trying to understand how "pointer to member" works but not everything is clear for me.

Here is an example class:

class T
{
public:
    int a;
    int b[10]; 
    void fun(){}
};

The following code ilustrate the problem and contains questions:

void fun(){};

void main()
{
   T obj;                
   int local;
   int arr[10];
   int arrArr[10][10];

   int *p = &local;   // "standard" pointer
   int T::*p = &T::a; // "pointer to member" + "T::" , that is clear

   void (*pF)() = fun;        //here also everything is clear
   void (T::*pF)() = T::fun;  
   //or
   void (T::*pF)() = &T::fun;  

   int *pA = arr; // ok
   int T::*pA = T::b; // error

   int (T::*pA)[10] = T::b; // error
   int (T::*pA)[10] = &T::b; //works;

//1. Why "&" is needed for "T::b" ? For "standard" pointer an array name is the representation of the 
//   address of the first element of the array. 

//2. Why "&" is not needed for the pointer to member function ? For "standard" pointer a function name 
//   is the representation of the function address, so we can write &funName or just funName when assigning to the pointer. 
//   That's rule works there.

//3. Why the above pointer declaration looks like the following pointer declaration ?: 

   int (*pAA)[10] = arrArr; // Here a pointer is set to the array of arrays not to the array. 
   system("pause");
}
Irbis
  • 11,537
  • 6
  • 39
  • 68
  • I think you meant to ask `2. Why "&" is needed for the pointer to member function?`. I'd really recommend you to read about [array decay](http://stackoverflow.com/a/4810668/183120). – legends2k Oct 23 '13 at 12:33
  • `&` is *always* needed when creating pointers-to-members, be that data members or member functions. No exceptions. If your compiler "works" without `&` - it is a non-standard quirk of your compiler. For example, `void (T::*pF)() = T::fun;` is an error. The `&` is required. – AnT stands with Russia Oct 23 '13 at 15:19

8 Answers8

8

Why "&" is needed for "T::b" ?

Because the standard requires it. This is to distinguish it from accessing a static class member.

From a standard draft n3337, paragraph 5.3.1/4, emphasis mine:

A pointer to member is only formed when an explicit & is used and its operand is a qualified-id not enclosed in parentheses. [Note: that is, the expression &(qualified-id), where the qualified-id is enclosed in parentheses, does not form an expression of type “pointer to member.” Neither does qualified-id, because there is no implicit conversion from a qualified-id for a non-static member function to the type “pointer to member function” as there is from an lvalue of function type to the type “pointer to function” (4.3). Nor is &unqualified-id a pointer to member, even within the scope of the unqualified-id’s class. — end note]


For "standard" pointer an array name is the representation of the address of the first element of the array.

Not really. An array automatically converts to a pointer to first element, where required. The name of an array is an array, period.


Why "&" is not needed for the pointer to member function ?

It is needed. If your compiler allows it, it's got a bug. See the standardese above.


For "standard" pointer a function name is the representation of the function address, so we can write &funName or just funName when assigning to the pointer.

The same thing aplies here as for arrays. There's an automatic conversion but otherwise a function has got a function type.

Consider:

#include <iostream>

template<typename T, size_t N>
void foo(T (&)[N]) { std::cout << "array\n"; }

template<typename T>
void foo(T*) { std::cout << "pointer\n"; }

int main()
{
    int a[5];
    foo(a);
}

Output is array.

Likewise for functions pointers:

#include <iostream>

template<typename T>
struct X;

template<typename T, typename U>
struct X<T(U)> {
    void foo() { std::cout << "function\n"; }
};

template<typename T, typename U>
struct X<T(*)(U)> {
    void foo() { std::cout << "function pointer\n"; }
};

void bar(int) {}

int main()
{
    X<decltype(bar)> x;
    x.foo();
}

Output is function.


And a clarification about this, because I'm not sure what exactly your comment is meant to say:

int arrArr[10][10];
int (*pAA)[10] = arrArr; // Here a pointer is set to the array of arrays not to the array.

Again, array-to-pointer conversion. Note that the elements of arrArr are int[10]s. pAA points to the first element of arrArr which is an array of 10 ints located at &arrArr[0]. If you increment pAA it'll be equal to &arrArr[1] (so naming it pA would be more appropriate).

If you wanted a pointer to arrArr as a whole, you need to say:

int (*pAA)[10][10] = &arrArr;

Incrementing pAA will now take you just past the end of arrArr, that's 100 ints away.

jrok
  • 54,456
  • 9
  • 109
  • 141
4

I think the simplest thing is to forget about the class members for a moment, and recap pointers and decay.

int local;
int array[10];

int *p = &local; // "standard" pointer to int

There is a tendency for people to say that a "decayed pointer" is the same as a pointer to the array. But there is an important difference between arr and &arr. The former does not decay into the latter

int (*p_array_standard)[10] = &arr;

If you do &arr, you get a pointer to an array-of-10-ints. This is different from a pointer to an array-of-9-ints. And it's different from a pointer-to-int. sizeof(*p_array_standard) == 10 * sizeof(int).

If you want a pointer to the first element, i.e. a pointer to an int, with sizeof(*p) == sizeof(int)), then you can do:

int *p_standard = &(arr[0);

Everything so far is based on standard/explicit pointers.

There is a special rule in C which allows you to replace &(arr[0]) with arr. You can initialize an int* with &(arr[0]) or with arr. But if you actually want a pointer-to-array, you must do int (*p_array_standard)[10] = &arr;

I think the decaying could almost be dismissed as a piece of syntactic sugar. The decaying doesn't change the meaning of any existing code. It simply allows code that would otherwise be illegal to become legal.

int *p = arr; // assigning a pointer with an array.  Why should that work?
                  // It works, but only because of a special dispensation.

When an array decays, it decays to a pointer to a single element int [10] -> int*. It does not decay to a pointer to the array, that would be int (*p)[10].


Now, we can look at this line from your question:

int (T::*pA3)[10] = T::b; // error

Again, the class member is not relevant to understanding why this failed. The type on the left is a pointer-to-array-of-ints, not a pointer-to-int. Therefore, as we said earlier, decaying is not relevant and you need & to get the pointer-to-array-of-ints type.

A better question would be to ask why this doesn't work (Update: I see now that you did have this in your question.)

int T::*pA3 = T::b;

The right hand side looks like an array, and the left hand side is a pointer to a single element int *, and therefore you could reasonably ask: Why doesn't decay work here?

To understand why decay is difficult here, let's "undo" the syntactic sugar, and replace T::b with &(T::b[0]).

int T::*pA3 = &(T::b[0]);

I think this is the question that you're interested in. We've removed the decaying in order to focus on the real issue. This line works with non-member objects, why doesn't it work with member objects?

The simple answer is that the standard doesn't require it. Pointer-decay is a piece of syntactic sugar, and they simply didn't specify that it must work in cases like this.

Pointers-to-members are basically a little fussier than other pointers. They must point directly at the 'raw' entity as it appears in the object. (Sorry, I mean it should refer (indirectly) by encoding the offset between the start of the class and the location of this member. But I'm not very good at explaining this.) They can't point to sub-objects, such as the first element of the array, or indeed the second element of the array.

Q: Now I have a question of my own. Could pointer decay be extended to work on member arrays like this? I think it makes some sense. I'm not the only one to think of this! See this discussion for more. It's possible, and I guess there's nothing stopping a compiler from implementing it as an extension. Subobjects, including array members, are at a fixed offset from the start of the class, so this is pretty logical.

Community
  • 1
  • 1
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • "Pointer decay" for array members would be equivalent to allowing pointer-to-members to point *into* the subobjects of the entire object, e.g. allowing `int T::*` pointer to point to an individual element if a member array `int a[10]` inside class `T` (i.e. pointing to `T::a[0]` in case of decay is really no different than pointing to `T::a[5]`). This is a natural feature to desire, but it does not exist in the language. There's a question about that here on SO, which you might find interesting to read http://stackoverflow.com/questions/1929887/is-pointer-to-inner-struct-member-forbidden – AnT stands with Russia Oct 23 '13 at 16:50
  • Thanks, @AndreyT, I've added that link. Is there any chance of a compiler implementing this as an extension? – Aaron McDaid Oct 23 '13 at 17:07
2

The first thing to note is that arrays decay into pointers to the first element.

int T::*pA = T::b;

There are two issues here, or maybe one, or more than two... The first is the subexpression T::b. The b member variable is not static, and cannot be accessed with that syntax. For pointer to members you need to always use the address-of operator:

int T::*pa = &T::b;  // still wrong

Now the problem is that the right hand side has type int (T::*)[10] that does not match the left hand side, and that will fail to compile. If you fix the type on the left you get:

int (T::*pa)[10] = &T::b;

Which is correct. The confusion might have risen by the fact that arrays tend to decay to the first element, so maybe the issue was with the previous expression: int *p = a; which is transformed by the compiler into the more explicit int *p = &a[0];. Arrays and functions have a tendency to decay, but no other element in the language does. And T::b is not an array.


Edit: I skipped the part about functions...

   void (*pF)() = fun;        //here also everything is clear
   void (T::*pF)() = T::fun; 
   //or
   void (T::*pF)() = &T::fun;

It might not be as clear as it seems. The statement void (T::*pf)() = T::fun; is illegal in C++, the compiler you use is accepting it for no good reason. The correct code is the last one: void (T::*pf)() = &T::fun;.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
1

Why "&" is needed for "T::b" ?

Because that's how the language is specified. It was decided not to complicate the language with a member-to-pointer conversion just for the sake of saving a single character even though, for historical reasons, we have similar conversions for arrays and functions.

For "standard" pointer an array name is the representation of the address of the first element of the array.

No it isn't; it's convertible to a pointer to its first element due to an arcane conversion rule inherited from C. Unfortunately, that's given rise to a widespread (and wrong) belief that an array is a pointer. This kind of confusion is probably part of the reason for not introducing similar bizarre conversions for member pointers.

Why "&" is not needed for the pointer to member function ?

It is. However, your compiler accepts the incorrect void main(), so it may accept other broken code.

For "standard" pointer a function name is the representation of the function address, so we can write &funName or just funName when assigning to the pointer.

Again, the function name isn't a pointer; it's just convertible to one.

Why the above pointer declaration looks like the following pointer declaration ?

One is a pointer to an array, the other is a pointer to a member array. They are quite similar, and so look quite similar, apart from the difference which indicates that one's a member pointer and the other's a normal pointer.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
1
int (T::*pA)[10] = &T::b; //works;

3.Why the above pointer declaration looks like the following pointer declaration ?

int (*pAA)[10] = arrArr;

To understand this, we needn't confuse ourselves with member arrays, simple arrays are good enough. Say've we two

int a[5];
int a_of_a[10][5];

The first (left-most) dimension of the array decays and we get a pointer to the first element of the array, when we use just the array's name. E.g.

int *pa = a;                 // first element is an int for "a"
int (*pa_of_a)[5] = a_of_a;  // first element is an array of 5 ints for "a_of_a"

So without using & operator on the array, when we assign its name to pointers, or pass it to function as arguments, it decays as explained and gives a pointer to its first element. However, when we use the & operator, the decay doesn't happen since we're asking for the address of the array and not using the array name as-is. Thus the pointer we get would be to the actual type of the array without any decay. E.g.

int (*paa) [5] = &a;                   // note the '&'
int (*paa_of_a) [10][5] = &a_of_a;

Now in your question the upper declaration is a pointer to an array's address without the decay (one dimension stays one dimension), while the lower declaration is a pointer to an array name with decay (two dimensions become one dimension). Thus both the pointers are to an array of same single dimension and look the same. In our example

int (*pa_of_a)[5]
int (*paa) [5]

notice that the types of these pointers are the same int (*) [5] although the value they point to are of different array's.

legends2k
  • 31,634
  • 25
  • 118
  • 222
0

Because T on it's own already has a well defined meaning: the type Class T. So things like T::b are logically used to mean members of Class T. To get the address of these members we need more syntax, namely &T::b. These factors don't come into play with free functions and arrays.

Paul Evans
  • 27,315
  • 3
  • 37
  • 54
0

A pointer to a class or struct type points to an object in memory.
A pointer to a member of a class type actually points to an offset from the start of the object.
You can think of these kind of pointers as pointers to blocks of memory. These need an actual address and offset, hence the &.

A pointer to function points to the access point of the function in the assembly code. A member method in general is the same as a function that passes a this pointer as the first argument.

That's in crude nut shell the logic behind needing a & to get the address for members and object address in general.

Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185
-1
void (*pF)() = fun;        //here also everything is clear

It doesn't work because function fun is undefined

int T::*pA = T::b; // error

What is T::b? T::b is not static member. So you need specific object. Instead write

int *pA = &obj.b[0]; 

Similarly,

int (T::*pA)[10] = &T::b; //works;

It can be compiled. But it will not work as you expected. Make b static or call obj.b to get access to defined member of defined object. We can easily check this. Create conctructor for your class T

class T
{
public:
    T() {
        a = 444;
    }
    int a;
    int b[10]; 
    void fun(){}
};

On what value points pA ?

 int T::*pA = &T::a; 

*pA doesn't not point on variable with value 444, because no object has been created, no constructor has been called.

Qwerty
  • 153
  • 1
  • 9
  • I think you misunderstood the last case. It is a pointer to member, it will compile and it will do what the OP wants, assuming that his/her statement about understanding pointers to members is somehow true. – David Rodríguez - dribeas Oct 23 '13 at 12:36
  • @DavidRodríguez may be, but int (T::*pA)[10] = &T::b; is meaningless construction. I've updated my answer – Qwerty Oct 23 '13 at 12:48
  • 2
    I am not sure you understand the concept of a *pointer-to-member*, which differs from having a pointer that refers to a member of a particular object. – David Rodríguez - dribeas Oct 23 '13 at 13:31
  • -1 http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/topic/com.ibm.xlcpp8l.doc/language/ref/cplr034.htm – kotlomoy Oct 23 '13 at 15:39