Diamond层次结构中的虚函数重载在clang和gcc中产生不同的结果

bre*_*ker 16 c++ gcc clang language-lawyer

以下代码在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;
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

只有方法C<T>::foo(Dummy1)B<T>::foo(Dummy2)选择,方法A<T>::foo不使用。

A::foo(Dummy2)当通过D<T>对象调用时(仅在那时)才选择Clang(版本4.0.0-8.0.0)。通过引用B<T>-进行调用时C<T>::foo(Dummy2)被选中。

B
A <-- difference
B
C
B
C
B
C
Run Code Online (Sandbox Code Playgroud)

当派生类的顺序更改为时struct D : C<T>, B<T>,输出更改为:

A <--
C
B
C
B
C
B
C
Run Code Online (Sandbox Code Playgroud)

似乎对于第二个派生类方法foo不视为虚拟方法。

仅有Visual Studio发出警告的C4250没有帮助。

写入using B<T>::foo;using C<T>::foo;in D<T>代替using A::foo;使clang产生以下错误: error: call to member function 'foo' is ambiguous。在gcc上,行为不会改变,代码编译和输出是相同的。

什么是正确的行为?

由于应用程序给出的结果不同,是否有办法找到该构造的所有相似实例或采取一些解决方法?我必须同时编译gcc和clang。检查是否在比我发现的更多的地方存在相同的问题可能很难。

小智 1

我相信这里发生了两件事。

首先,clang 的实现肯定存在问题,因此提交错误报告是一个很好的举措。

D但是当你在引用上调用 foo 时也会出现一个问题

D<int> d;
d.foo(Dummy1{});
d.foo(Dummy2{});
Run Code Online (Sandbox Code Playgroud)

Visual Studio 给您的警告实际上非常准确:从技术上讲,每次调用都有两个选项,但通过dominance会选择一个选项。

对于Dummy1重载,有 class 中的重写实现B,但也有class 中的未重写C实现。这就是警告中所说的“主导”一方隐藏另一方的情况。在这种情况下,主导版本是 class 中被覆盖的版本B,而弱版本是 class 中的版本C。如果您也覆盖了Dummy1类中的重载C,那么您将得到一个不明确的调用。

对于过载也是如此Dummy2

因此,编译器警告您的是,在已知D实例的情况下,您实际上有一个选择,并且应该对此明确。