4

我对成员指针有疑问。以下代码无法同时使用 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++ 做了一个隐式向上转换 from int A::*toint B::*来满足对成员函数模板的调用,其他两个编译器不应该考虑做同样的事情吗?

4

1 回答 1

8

允许从int A::*to转换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 具有表单模板 ID,则 A 可以是推导出的 A 的派生类。同样,如果 P 是指向表单模板 ID 的类的指针,则 A 可以是指向由推导的 A 指向的派生类。

这里“P”是模板函数的参数类型:M B::*p,其中模板类型参数M要确定。“A”是实际参数的类型:int A::*. P 和 A 当然不是引用或类,我们需要的指针到成员的转换不是限定转换(它只描述了像X*toconst X*int X::*to这样的 const/volatile 操作const int X::*)。

所以不能推导出模板参数,你应该在<int>代码中添加显式模板参数。

于 2010-10-04T23:11:48.150 回答