【问题标题】:Virtual function overloading in diamond hierarchy produces different results in clang and gcc菱形层次结构中的虚函数重载在clang和gcc中产生不同的结果
【发布时间】:2019-10-20 11:50:50
【问题描述】:

以下代码在 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 Preview 1.0、Visual Studio 2013 12.0.31101.00 Update 4、clang(版本 3.4.1 - 3.9。 1)

全部给出以下输出,这是我所期望的:

B
C
B
C
B
C
B
C

只选择方法C&lt;T&gt;::foo(Dummy1)B&lt;T&gt;::foo(Dummy2),不使用方法A&lt;T&gt;::foo

Clang(版本 4.0.0 - 8.0.0)在通过 D&lt;T&gt; 对象调用时选择 A::foo(Dummy2),并且仅在那时。通过引用 B&lt;T&gt; 调用时 - 选择 C&lt;T&gt;::foo(Dummy2)

B
A <-- difference
B
C
B
C
B
C

当派生类的顺序变为struct D : C&lt;T&gt;, B&lt;T&gt;时,输出变为:

A <--
C
B
C
B
C
B
C

似乎对于第二个派生类方法foo 不被认为是虚拟的。

只有 Visual Studio 会发出警告,而 C4250 没有那么有用。

using B&lt;T&gt;::foo;using C&lt;T&gt;::foo; 写入D&lt;T&gt; 而不是using A::foo; 会使clang 产生以下错误:

错误:对成员函数 'foo' 的调用不明确

在 gcc 上的行为没有改变,代码编译和输出是一样的。

这里的正确行为是什么?

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

【问题讨论】:

  • 对于显示的程序,clang 绝对是错误的。对于在D 中有两个using 声明的版本,[namespace.udecl]/15 远不清楚会发生什么。前一个 using-declarator 的成员是否“在派生类中”?
  • 我也认为是clang bug;通过不合格的函数调用选择A::foo 不应禁用虚拟调度
  • 这是另一个不使用函数重载的测试用例wandbox.org/permlink/NGGfh7OOnRveO2zN 函数覆盖似乎被忽略,除非它们位于第一个继承的节点中,但如果你没有using 或者您正在使用对基类的引用。
  • 我将错误报告添加到 clang 的问题跟踪器 bugs.llvm.org/show_bug.cgi?id=42211

标签: c++ gcc clang overloading language-lawyer


【解决方案1】:

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

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

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

D<int> d;
d.foo(Dummy1{});
d.foo(Dummy2{});

Visual Studio 给你的警告实际上非常准确:从技术上讲,每次调用都有两个选项,但通过支配地位选择一个而不是另一个。

对于Dummy1 重载,有B 类中的重写实现,还有C 类中的未重写 实现。这就是警告所指的“主导”一个隐藏另一个。 在这种情况下,主要版本是 B 类中的覆盖版本,而弱版本是 C 类中的版本。如果您还覆盖了 C 类中的 Dummy1 重载,那么您的调用就会模棱两可。

Dummy2 重载也是如此。

所以编译器警告你的是,对于已知的D 实例,你实际上有一个选择并且应该明确说明。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-25
    • 1970-01-01
    • 2016-01-08
    • 1970-01-01
    • 2019-08-25
    • 2020-12-08
    相关资源
    最近更新 更多