指向基类成员的指针类型

时间:2010-10-04 22:07:08

标签: c++ member-pointers

我有关于成员指针的问题。以下代码无法使用Oracle Solaris Studio 12.2的CC和cygwin GCC 4.3.4进行编译,但无法与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;
}

在倒数第二行,上面提到的两个编译器都找不到Bar<B>::foo(int A::*)的匹配项。我写了一个简单的测试来确认表达式&B::x的类型实际上是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;
}

以下解决方法适用于GCC(尚未使用Oracle CC进行测试)但由于含糊不清而导致VC ++失败:

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));
  }
};

我的问题: 哪种行为是正确的?显然VC ++从int A::*int B::*执行隐式向上转换以满足对成员函数模板的调用,其他两个编译器是否应该考虑做同样的事情呢?

1 个答案:

答案 0 :(得分:7)

允许从int A::*转换为int B::*,这不是问题所在。问题在于模板参数推导,因为您可以看到是否尝试以下程序为<int>提供模板参数B::foo并进行编译,以及生成非成员函数foo2B::foo之前的错误相同。

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;
}

我认为编译器应该自己推导出模板参数<int>的情况不包括这种情况。 14.8.2.1p3:

  

通常,推导过程会尝试查找模板参数值,这些参数值将使推导出的A与A相同(在如上所述转换类型A之后)。但是,有三种情况可以产生差异:

  • 如果原始P是引用类型,则推导出的A(即引用所指的类型)可以比A更符合cv。
  • A可以是另一个指向成员类型的指针或指针,可以通过限定转换(conv.qual)转换为推导出的A.
  • 如果P是一个类,并且P具有表单template-id,则A可以是推导出的A的派生类。同样,如果P是指向表单template-id的类的指针,则A可以是指向推导出的A指向的派生类的指针。

此处“P”是模板函数的参数类型:M B::*p,其中要确定模板类型参数M。 “A”是实际参数的类型:int A::*。 P和A当然不是引用或类,并且我们需要这种指针到成员的转换类型不是限定转换(它仅描述X*到{const X*之类的常量/易失性操作{1}}或int X::*const int X::*)。

因此无法推断出模板参数,您应该将<int>显式模板参数添加到代码中。