【问题标题】:Ambiguous when two superclasses have a member function with the same name, but different signatures当两个超类具有同名但签名不同的成员函数时不明确
【发布时间】:2012-04-02 11:08:14
【问题描述】:
struct A {
    void f(int x) {}
};

struct B {
    template<typename T> void f(T x) {}
};

struct C : public A, public B {};

struct D {
    void f(int x){}
    template<typename T> void f(T x) {} 
};


int main(int argc, char **argv) {
    C c;
    c.f<int>(3);
    D d;
    d.f<int>(3);
}

什么原因调用d.f就好了,但是c.f给了

error: request for member ‘f’ is ambiguous
error: candidates are: template<class T> void B::f(T)
error:                 void A::f(int)

【问题讨论】:

  • 好问题。我想通常的重载解决规则不适用于C(因为如果有匹配项,通常非模板优先于模板,因此D 的行为)。
  • 我想补充 OP 的问题:“无论 C++ 标准规则强制执行这种行为:该规则背后的基本原理是什么?”
  • @Vlad 我认为这种行为是非常明智的。在这里不引起错误可能会导致许多讨厌的错误。好问题。
  • @enobayram:那么,为什么C::f 没有错误?
  • 你确定编译器在第一次错误后没有停止吗?

标签: c++ templates class-hierarchy


【解决方案1】:

第一部分是由于成员名称查找,这就是它失败的原因。

我会推荐你​​:10.2/2 Member name lookup

以下步骤定义类范围内名称查找的结果, C.首先,每个类中的名称和每个声明 考虑其基类子对象。成员名f合一 如果 A 是基础,则子对象 B 在子对象 A 中隐藏成员名称 f B 的类子对象。任何如此隐藏的声明都是 排除在外。这些声明中的每一个 由 using 声明引入的被认为是来自每个 C 的子对象,属于包含声明的类型 由 using 声明指定。

如果声明的结果集并非全部来自 相同的类型,或者集合具有非静态成员并包含成员 从不同的子对象,有一个歧义,程序是 格式错误。否则,该集合就是查找的结果。

现在,关于模板函数的问题。

根据13.3.1/7 Candidate functions and argument list

在候选是函数模板的每种情况下,候选 函数模板特化是使用模板生成的 论据推论 (14.8.3, 14.8.2)。然后处理这些候选人 以通常的方式作为候选函数。给定名称可以指代一个 或更多功能模板以及一组重载的 非模板函数。在这种情况下,候选函数 从每个函数模板生成的集合与 非模板候选函数。

如果你继续阅读13.3.3/1 Best viable function

F1 被认为是一个更好的函数,如果:

F1 是非模板函数,F2 是函数模板 专业化

这就是为什么下面的sn-p编译运行非模板函数没有错误的原因:

D c;
c.f(1);

【讨论】:

  • 但是为什么编译器只看到A::f,却看不到B::f?命名空间的区别在哪里?
  • 你能引用标准中的一句话来支持吗?
  • 目前我正在假设它一旦找到一个就会忽略所有其他基本成员。想知道您是否可以通过更改继承顺序来控制正在调用的内容,例如struct C : public B, public A
  • @LuchianGrigore 我正在寻找它。
  • @Vlad:在C的情况下编译器不会消除它们中的任何一个;第二段适用,并且程序格式错误,因为它们并非都来自同一类型的子对象。
【解决方案2】:

我相信编译器无缘无故更喜欢A::f(非模板函数)而不是B::f
这似乎是一个编译器实现错误,而不是依赖于实现的细节。

如果添加以下行,则 compilation goes fine 和正确的函数 B::f&lt;&gt; 被选中:

struct C : public A, public B { 
  using A::f; // optional
  using B::f;
};

[有趣的是,直到::f 没有被纳入C 的范围内,它们才被视为外来函数。]

【讨论】:

  • Iteresting 仍然存在,如果我们注释掉 using A::f; A::f 被 B::f 隐藏,而 using 声明 c.f&lt;int&gt;(3)c.f(3) 正确调用相应的函数。
  • 除了编译器不喜欢A::f - 它考虑了两者,并且由于歧义而失败。
  • @MikeSeymour,那么问题是为什么编译器在 D::f 的情况下没有这种歧义?在这种情况下,这似乎是我偏爱的情况。
  • @iammilind:确实;在D 的情况下,两个名称都被考虑,并且模板特化优先于非模板。我在评论您的陈述,即“编译器更喜欢 A::f 而不是 B::f”,这是错误的 - 在这种情况下,它也不喜欢,因此出现歧义错误。
【解决方案3】:

编译器不知道从 C 类调用哪个方法,因为在 int 类型的情况下,模板化方法将在 void f(int) 中进行转换,因此您有两个具有相同名称和相同参数但成员不同的方法父类。

template<typename T> void f(T x) {} 

void f(int)

试试这个:

c.B::f<int>(3);

或者这个对于 A 类:

c.A::f(3);

【讨论】:

  • OP的问题不是如何,而是why
  • 为什么的答案是:编译器不知道从 C 类调用哪个方法
  • 好吧...为什么它知道 D 类?
  • 我猜编译器会在继承之前应用模板构造。 B 和 A 类将首先编译,然后将应用 C 类的继承。
  • 有趣,如果我在 Cs 声明中说 using A::f; using B::f;,问题就消失了。
【解决方案4】:

考虑这个更简单的例子:

struct A{
 void f(int x){}
};

struct B{
 void f(float t){}
};


struct C:public A,public B{
};

struct D{
 void f(float n){}
 void f(int n){}
};


int main(){
 C c;
 c.f(3);

 D d;
 d.f(3);
}

在这个例子中,和你的一样,D 编译但是C 没有。
如果一个类是派生类,则成员查找机制的行为会有所不同。它检查每个基类并将它们合并:在C 的情况下;每个基类都匹配查找( A::f(int) 和 B::f(float) )。合并它们后C 认为它们是模棱两可的。

对于案例类D:选择int 版本而不是float,因为参数是整数。

【讨论】:

  • 谢谢,这支持了当它没有找到方法时,它会向上到超类,生成所有的可能性,如果有多个选择就会抱怨。正如@jammilind 和 jrok 指出的那样,使用它预先生成它们,并检查最匹配的一个
  • 这只是一个观察。如果没有来自标准的支持引用,它是没有用的。这可能是一个编译器错误。
  • @LuchianGrigore 是的,这就是我等待切换已接受答案的原因
  • @FabioDallaLibera 你也应该这样做。这个问题非常好,我认为它值得一个匹配的答案。
  • 可以在 C++ 标准成员名称查找部分(class.member.lookup item 6)中观察到。对不起,我不能在这里复制,但它也包含比我更好的描述。
【解决方案5】:

可能发生的情况是模板实例化分别针对类AB 进行,因此以两个void f(int) 函数结束。

这在 D 中不会发生,因为编译器知道 void f(int) 函数是一种特化,因此不会将 T 特化为 int

【讨论】:

    猜你喜欢
    • 2010-09-29
    • 2013-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多