views:

96

answers:

1

I have a problem regarding member pointers. The following code fails to compile using both Oracle Solaris Studio 12.2's CC and cygwin GCC 4.3.4 but works with Microsoft Visual C++ 2010:

struct A {
  int x;
};

struct B : public A {
};

template<typename T> class Bar {
public:
  template<typename M> void foo(M T::*p);
};

int main(int, char *[]) {
    Bar<B> bbar;
    bbar.foo(&B::x);
    return 0;
}

At the next to last line both compilers mentioned above fail to find a match for Bar<B>::foo(int A::*). I wrote a simple test to confirm that the type of the expression &B::x is actually int A::*:

// ...

static void foo(int A::*p) {
  std::cout << "A" << std::endl;
}

static void foo(int B::*p) {
  std::cout << "B" << std::endl;
}

int main(int, char *[]) {
    foo(&B::x);  // prints "A", even on MS VC++ 2010 
    return 0;
}

The following workaround works with GCC (not tested with Oracle CC yet) but fails with VC++ due to ambiguity:

template<typename T> class Bar {
public:
  template<typename M> void foo(M T::*p);
  template<typename M, typename _T_base> inline void foo(M _T_base::*p) {
      foo(static_cast<M T::*>(p));
  }
};

My question: Which behavior is correct? Apparently VC++ does an implicit upcast from int A::* to int B::* to satisfy the call to the member function template, shouldn't the other two compilers consider doing the same?

+4  A: 

A conversion from int A::* to int B::* is allowed, and that's not the problem. The problem is in template argument deduction, as you can see if you try the following program which supplies a template argument <int> for B::foo and compiles, and a non-member function foo2 which produces the same error as B::foo did before.

struct A {
  int x;
};

struct B : public A {
};

template <typename T> class Bar {
public:
  template<typename M> void foo(M T::*p);
};

template<typename M> void foo2(M B::*p);

int main(int, char*[]) {
  Bar<B> bbar;
  bbar.foo<int>(&B::x);
  foo2(&B::x); // error, but foo2<int>(&B::x) would work.
  return 0;
}

I think this situation is not covered by the cases where the compiler is supposed to deduce the template argument <int> on its own. 14.8.2.1p3:

In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:

  • If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than A.
  • A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification conversion (conv.qual).
  • If P is a class, and P has the form template-id, then A can be a derived class of the deduced A. Likewise, if P is a pointer to a class of the form template-id, A can be a pointer to a derived class pointed to by the deduced A.

Here "P" is the template function's argument type: M B::*p, where template type parameter M is to be determined. "A" is the type of the actual argument: int A::*. P and A are certainly not a reference or a class, and the sort of pointer-to-member conversion we would need for this to work is not a qualification conversion (which describes only const/volatile manipulations like X* to const X* or int X::* to const int X::*).

So the template argument cannot be deduced, and you should add the <int> explicit template parameter to your code.

aschepler
Deduction can't happen anyway when you also need to convert. Between this and Dummy's comment, I think this is pretty well answered. +1
Potatoswatter
Actually, template parameter deduction and argument conversion is allowed in a few situations. For example, `template <typename T> void f(const T*); void g(int* ptr) { f(ptr); }` is legal, and the compiler must deduce `T` is `int` and convert `ptr` to `const int*`.
aschepler