Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to understand "pointer to member"

Tags:

c++

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");
}
like image 693
Irbis Avatar asked Oct 23 '13 12:10

Irbis


People also ask

What is pointer to member Declarator in C++?

1) Pointer declarator: the declaration S* D; declares D as a pointer to the type determined by decl-specifier-seq S . 2) Pointer to member declarator: the declaration S C::* D; declares D as a pointer to non-static member of C of type determined by decl-specifier-seq S .

How do I access the member of a pointer?

To access a member function by pointer, we have to declare a pointer to the object and initialize it (by creating the memory at runtime, yes! We can use new keyboard for this). The second step, use arrow operator -> to access the member function using the pointer to the object.

Which do you understand by pointers to member?

Pointers to members allow you to refer to nonstatic members of class objects. You cannot use a pointer to member to point to a static class member because the address of a static member is not associated with any particular object. To point to a static class member, you must use a normal pointer.

Which is the pointer to a member of a class operator?

There are two pointer to member operators: . * and ->* . The . * operator is used to dereference pointers to class members.


2 Answers

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.

like image 200
jrok Avatar answered Sep 21 '22 12:09

jrok


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.

like image 43
Aaron McDaid Avatar answered Sep 20 '22 12:09

Aaron McDaid