下面的代码在clang上产生不同的结果。
#include <iostream>
struct Dummy1 {};
struct Dummy2 {};
struct A {
virtual void foo(Dummy1) {
std::cout << "A" << std::endl;
}
virtual void foo(Dummy2) {
std::cout << "A" << std::endl;
}
};
template<class T>
struct C : virtual A {
using A::foo;
void foo(Dummy2) override {
std::cout << "C" << std::endl;
}
};
template<class T>
struct B : virtual A {
using A::foo;
void foo(Dummy1) final {
std::cout << "B" << std::endl;
}
};
template<class T>
struct D : B<T>, C<T> {
// using B<T>::foo; // error: call to member function 'foo' is ambiguous
// using C<T>::foo; // error: call to member function 'foo' is ambiguous
using A::foo;
};
int main() {
D<int> d;
d.foo(Dummy1{});
d.foo(Dummy2{});
A& a = d;
a.foo(Dummy1{});
a.foo(Dummy2{});
B<int>& b = d;
b.foo(Dummy1{});
b.foo(Dummy2{});
C<int>& c =d;
c.foo(Dummy1{});
c.foo(Dummy2{});
return 0;
}
gcc(版本4.8.1-9.1),icc(版本16、17、19),Visual Studio 2017 15.4.0预览版1.0,Visual Studio 2013 12.0.31101.00更新4,clang(版本3.4.1-3.9。 1)
全部给出以下输出,这是我期望的:
B
C
B
C
B
C
B
C
仅选择方法C<T>::foo(Dummy1)
和B<T>::foo(Dummy2)
,不使用方法A<T>::foo
。
Clang(版本4.0.0-8.0.0)仅在通过A::foo(Dummy2)
对象调用时选择D<T>
。通过引用B<T>
进行调用时-选择了C<T>::foo(Dummy2)
。
B
A <-- difference
B
C
B
C
B
C
将派生类的顺序更改为struct D : C<T>, B<T>
时,输出更改为:
A <--
C
B
C
B
C
B
C
对于第二个派生类方法foo
似乎不被视为虚拟方法。
只有Visual Studio发出警告时没有提供有用的C4250。
在using B<T>::foo;
中使用using C<T>::foo;
和D<T>
而不是using A::foo;
会使c产生以下错误:
error: call to member function 'foo' is ambiguous
。
在gcc上,行为不会改变,代码编译和输出是相同的。
这里的正确行为是什么?
由于应用程序给出的结果不同,是否可以找到该构造的所有相似实例或采取一些解决方法?我必须同时编译gcc和clang。检查是否在比我发现的更多的地方存在相同的问题可能很难。
答案 0 :(得分:0)
我相信这里发生了两件事。
首先,clang的实现肯定存在问题,因此提交错误报告是一个不错的选择。
但是当您在D
引用上调用foo时,也会出现问题
D<int> d;
d.foo(Dummy1{});
d.foo(Dummy2{});
Visual Studio发出的警告实际上非常准确:每个呼叫在技术上都有两个选项,但是通过 dominance 会优先选择另一个。
对于Dummy1
重载,在类B
中有重写的实现,还有在类C
中的 not-overridden 实现。这就是警告所指的是“主要”一个隐藏另一个。
在这种情况下,主要版本是B
类中的重写版本,而弱版本是C
类中的替代版本。如果您还覆盖了Dummy1
类中的C
重载,那么您将有一个模棱两可的调用。
类似的情况对于Dummy2
重载也适用。
因此,编译器警告您的是,在已知的D
实例的情况下,您实际上可以选择,并且应该对此明确。