Type of address-of-member expression
8.3.1 [expr.unary.op]
Lisa Lippincott

Created on 2000-02-08.00:00:00 last changed 63 months ago


Date: 2015-04-13.00:00:00

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.

Date: 2012-10-15.00:00:00

Rationale (October, 2012):

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

Date: 2012-09-15.00:00:00

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


  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 17.3.2 [temp.arg.nontype] paragraph 5) 
    call(&get<Base, int, &Derived::a>);    //Template function is instantiated properly but has invalid type


  struct Base { int a; };
  struct Derived : private Base {
    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
Date: 2000-04-15.00:00:00

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 {
      int func() const;

    class Derived : public Base {

    template<class T>
    class Templ {
      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.)

Date: 2000-02-08.00:00:00

8.3.1 [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: 8.3.1 [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::*
Date User Action Args
2015-04-13 00:00:00adminsetmessages: + msg5428
2015-04-13 00:00:00adminsetstatus: extension -> nad
2012-11-03 00:00:00adminsetmessages: + msg4188
2012-11-03 00:00:00adminsetstatus: open -> extension
2012-09-24 00:00:00adminsetmessages: + msg3924
2000-05-21 00:00:00adminsetmessages: + msg353
2000-02-08 00:00:00admincreate