Question about using auto for type inference of member function pointer

Viewed 65

My question is about member function pointer. Here a sample code: class C inherits foo from A and bar from B. I expected both &C::foo and &C::bar are of the same type void (C::*)(), but actually they are different types, one is void(A::*)() and the other is void(B::*)(). I used gdb ptype command to inspected data types.

My question is: is this behavior defined by C++ standards? What is the rationale of this design decision?

#include <type_traits>
#include <iostream>

struct A {
  void foo() {
    std::cout << this << std::endl;
  }
  int a;
};

struct B {
  void bar() {
    std::cout << this << std::endl;
  }
  int b;
};

struct C : public A, B { };

int main() {
  auto p1 = &C::foo;  // p1 is void (A::*)();
  auto p2 = &C::bar;  // p2 is void (B::*)();

  auto p3 = &A::foo;  // p3 is void (A::*)();

  bool b1 = std::is_same<decltype(p1), decltype(p2)>::value;
  bool b2 = std::is_same<decltype(p1), decltype(p3)>::value;
  std::cout << b1 << std::endl; // false
  std::cout << b2 << std::endl; // true
  return 0;
}
2 Answers

Yes, this is specified by the standard:

When you have a class that inherits from other classes, the members of those classes are not members of the derived class. They are still members of their classes, and the derived class inherits them. So, the members of A and B are still members of A and B, and that's still reflected in their type, where appropriate. This is pretty much a core principle of C++, and you are simply observing this in action.

Just like when you put a bunch of books on your bookshelf, the chapters in those books do not become a part of the bookshelf. They are still the same chapters in the book, and your bookshelf is the means to access them.

Similarly, you can use C to access members of its parent classes, A and B, using the name of the class C. But they are still members of A and B, and it's reflected in their type.

When use use auto, you get the exact type of your expression. C++ allows you to convert a pointer to a member of a base class into a pointer to a member of a derived class. However, this is a conversion. A conversion does not happen when using auto because that's exactly what auto is for: use whatever the actual type of the expression is, to initialize/construct the object, and don't do any conversions (there are some fine details here that add a few twists here, with respect to references, but it's not germane to this discussion).

The rationale behind this choice is the fact that the less you are assuming about a type, the better it is.

C is a A, so (A::foo*)() is the most generic type for the member function inherited from A.

Why would you want to enforce a stricter type? It's a win-win situation.

Related