7

I'm trying to understand the consistency in the error that is thrown in this program:

#include <iostream>

class A{
public:
    void test();
    int x = 10;

};

void A::test(){

    std::cout << x << std::endl; //(1)
    std::cout << A::x << std::endl; //(2)

    int* p = &x;
    //int* q = &A::x; //error: cannot convert 'int A::*' to 'int*' in initialization| //(3)


}

int main(){

    const int A::* a = &A::x; //(4)

    A b;

    b.test();

}

The output is 10 10. I labelled 4 points of the program, but (3) is my biggest concern:

  1. x is fetched normally from inside a member function.
  2. x of the object is fetched using the scope operator and an lvalue to the object x is returned.
  3. Given A::x returned an int lvalue in (2), why then does &A::x return not int* but instead returns int A::*? The scope operator even takes precedence before the & operator so A::x should be run first, returning an int lvalue, before the address is taken. i.e. this should be the same as &(A::x) surely? (Adding parentheses does actually work by the way).
  4. A little different here of course, the scope operator referring to a class member but with no object to which is refers.

So why exactly does A::x not return the address of the object x but instead returns the address of the member, ignoring precedence of :: before &?

AntiElephant
  • 1,227
  • 10
  • 18

2 Answers2

8

Basically, it's just that the syntax &A::x (without variation) has been chosen to mean pointer-to-member.

If you write, for example, &(A::x), you will get the plain pointer you expect.

More information on pointers-to-members, including a note about this very property, can be found here.

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • I think if it worked otherwise, it would lead to more confusion. The same sequence of identifiers and operators, referencing the exact same types and members, meaning two different things depending on scope: sounds like trouble to me. – John Sensebe Jan 21 '16 at 22:49
  • @JohnSensebe definitely. I originally wrote "quirk", but this is in fact a very sensible choice, enven though it can seem weird at first. – Quentin Jan 21 '16 at 22:52
3

The C++ standard doesn't explicitly specify operator precedence; it's possible to deduce operator precedence implicitly from the grammar rules, but this approach fails to appreciate the occasional special case like this which doesn't fit into a traditional model of operator precedence.

[expr.unary.op]/3:

The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. If the operand is a qualified-id naming a non-static or variant member m of some class C with type T, the result has type 'pointer to member of class C of type T' and is a prvalue designating C::m. Otherwise, if the type of the expression is T, the result has type 'pointer to T' and is a prvalue that is the address of the designated object or a pointer to the designated function.

/4:

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'.

[expr.prim.general]/9:

A nested-name-specifier that denotes a class, optionally followed by the keyword template, and then followed by the name of a member of either that class or one of its base classes, is a qualified-id.

What it all adds up to is that an expression of the form &A::x has the type "pointer to member x of class A" if x is a non-static member of a non-union class A, and operator precedence has no influence on this.

Oktalist
  • 14,336
  • 3
  • 43
  • 63