This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 114a. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-04-18


203. Type of address-of-member expression

Section: 7.6.2.2  [expr.unary.op]     Status: NAD     Submitter: Lisa Lippincott     Date: 8 Feb 2000

7.6.2.2 [expr.unary.op] paragraph 2 indicates that the type of an address-of-member expression reflects the class in which the member was declared rather than the class identified in the nested-name-specifier of the qualified-id. This treatment is unintuitive and can lead to strange code and unexpected results. For instance, in

    struct B { int i; };
    struct D1: B { };
    struct D2: B { };

    int (D1::* pmD1) = &D2::i;   // NOT an error
More seriously, template argument deduction can give surprising results:
    struct A {
       int i;
       virtual void f() = 0;
    };

    struct B : A {
       int j;
       B() : j(5)  {}
       virtual void f();
    };

    struct C : B {
       C() { j = 10; }
    };

    template <class T>
    int DefaultValue( int (T::*m) ) {
       return T().*m;
    }

    ... DefaultValue( &B::i )    // Error: A is abstract
    ... DefaultValue( &C::j )    // returns 5, not 10.

Suggested resolution: 7.6.2.2 [expr.unary.op] should be changed to read,

If the member is a nonstatic member (perhaps by inheritance) of the class nominated by the nested-name-specifier of the qualified-id having type T, the type of the result is "pointer to member of class nested-name-specifier of type T."
and the comment in the example should be changed to read,
// has type int B::*

Notes from 04/00 meeting:

The rationale for the current treatment is to permit the widest possible use to be made of a given address-of-member expression. Since a pointer-to-base-member can be implicitly converted to a pointer-to-derived-member, making the type of the expression a pointer-to-base-member allows the result to initialize or be assigned to either a pointer-to-base-member or a pointer-to-derived-member. Accepting this proposal would allow only the latter use.

Additional notes:

Another problematic example has been mentioned:

    class Base {
    public:
      int func() const;
    };

    class Derived : public Base {
    };

    template<class T>
    class Templ {
    public:
      template<class S>
      Templ(S (T::*ptmf)() const);
    };

    void foo()
    {
      Templ<Derived> x(&Derived::func);    // ill-formed
    }

In this example, even though the conversion of &Derived::func to int (Derived::*)() const is permitted, the initialization of x cannot be done because template argument deduction for the constructor fails.

If the suggested resolution were adopted, the amount of code broken by the change might be reduced by adding an implicit conversion from pointer-to-derived-member to pointer-to-base-member for appropriate address-of-member expressions (not for arbitrary pointers to members, of course).

(See also issues 247 and 1121.)

Additional notes (September, 2012):

Tomasz Kamiński pointed out three additional motivating examples:

  struct Very_base { int a; };
  struct Base1 : Very_base {};
  struct Base2 : Very_base {};
  struct Derived : Base1, Base2 {}

  int main() {
    Derived d;
    int Derived:: * a_ptr = &Derived::Base1::a; //error: Very_base ambiguous despite qualification
  };

Also:

  struct Base { int a; };
  struct Derived : Base { int b; };

  template<typename Class, typename Member_type, Member_type Base:: * ptr>
  Member_type get(Class &c) { return c.*ptr; }

  void call(int (*f)(Derived &));

  int main() {
    call(&get<Derived, int, &Derived::b>); // Works correctly
    call(&get<Derived, int, &Derived::a>); // Fails because &Derived::a returns an int Base::*
                                           // and no conversions are applied to pointer to member
                                           // (as specified in 13.4.3 [temp.arg.nontype] paragraph 5)
    call(&get<Base, int, &Derived::a>);    //Template function is instantiated properly but has invalid type
  }

Finally:

  struct Base { int a; };
  struct Derived : private Base {
  public:
    using Base::a; //make a accessible
  };

  int main() {
    Derived d;
    d.a; // valid
    int Derived::* ptr = &Derived::a; // Conversion from int Base::* to int Derived::*
                                      // is ill-formed because the base class is inaccessible
  }

Rationale (October, 2012):

CWG felt that such a change to the existing semantics would be better considered by EWG rather than as a defect.

Additional note, April, 2015:

EWG has determined that the utility of such a change is outweighed by the fact that it would break code. See EWG issue 89.