【问题标题】:Type of pointer to member from base class基类成员的指针类型
【发布时间】:2011-04-21 01:05:45
【问题描述】:

我对成员指针有疑问。以下代码无法同时使用 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&lt;B&gt;::foo(int A::*) 的匹配项。我写了一个简单的测试来确认表达式&amp;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::* 以满足对成员函数模板的调用,其他两个编译器不应该考虑这样做吗?

【问题讨论】:

  • 对答案也很感兴趣,因为我自己也遇到过类似的问题。我的理解是,由于多重继承,&amp;B::x 被静默转换为&amp;A::x 的期望是错误的:B 的实例可能包含A 的多个实例,因此&amp;B::x (可能)是模棱两可的。跨度>
  • 从我删除的答案中:static_cast&lt; int B::* &gt;( &amp;B::x ) 也有效。
  • @Dummy00001:我怀疑在存在多重继承的情况下,编译器的行为会像所有其他基类成员解析一样,并输出一个错误,说明名称不明确。如果编译器能够解析名称(它应该在这里),那么它应该毫不费力地解析它。
  • @Andre:问题是没有实体B::x。如果我们将其分解,则有限定符B::,它将我们带入B 的范围,然后是x,它查找开始 与B 的范围的对象。我们找到了一个对象x,如果我们找到两个x,确实会出错,但是x仍然关联A而不是B,因为它所在的范围是A的。

标签: c++ member-pointers


【解决方案1】:

允许从int A::* 转换为int B::*,这不是问题所在。问题出在模板参数推导中,如果您尝试以下程序,该程序为B::foo 提供模板参数&lt;int&gt; 并编译,以及产生与@987654326 相同的错误的非成员函数foo2,您会看到@以前做过。

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

我认为编译器应该自行推断模板参数&lt;int&gt; 的情况没有涵盖这种情况。 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*const X* 这样的 const/volatile 操作或int X::*const int X::*)。

所以不能推导出模板参数,你应该在你的代码中添加&lt;int&gt;显式模板参数。

【讨论】:

  • 当您还需要转换时,无论如何都不能进行扣除。在这和 Dummy 的评论之间,我认为这是很好的回答。 +1
  • 其实模板参数推导和参数转换在少数情况下是允许的。例如template &lt;typename T&gt; void f(const T*); void g(int* ptr) { f(ptr); }是合法的,编译器必须推导出Tint并将ptr转换为const int*
猜你喜欢
  • 1970-01-01
  • 2018-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-10
  • 2017-03-09
  • 2011-02-03
相关资源
最近更新 更多