Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Virtual function overloading in diamond hierarchy produces different results in clang and gcc

The following code produces different results on 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 (versions 4.8.1 - 9.1), icc (versions 16, 17, 19), Visual Studio 2017 15.4.0 Preview 1.0, Visual Studio 2013 12.0.31101.00 Update 4, clang (versions 3.4.1 - 3.9.1)

All give the following output, which is what I expect:

B
C
B
C
B
C
B
C

Only methods C<T>::foo(Dummy1) and B<T>::foo(Dummy2) are selected, methods A<T>::foo are not used.

Clang (versions 4.0.0 - 8.0.0) selects A::foo(Dummy2) when called through D<T> object and only then. When called through reference to B<T> - C<T>::foo(Dummy2) is selected.

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

When the order of the derived classes is changed to struct D : C<T>, B<T>, then output changes to:

A <--
C
B
C
B
C
B
C

It seems that for second derived class method foo is not considered virtual.

Only Visual Studio is giving warning with not that helpful C4250.

Writing using B<T>::foo; and using C<T>::foo; in D<T> instead of using A::foo; makes clang produce the following error:

error: call to member function 'foo' is ambiguous

On gcc behavior doesn't change, code compiles and output is the same.

What is correct behavior here?

Since application gives different results, is there a way to find all similar instances of this construction or make some workaround? I have to compile with both gcc and clang. Checking if same problem is present in more places than I found can be hard.

like image 682
breiker Avatar asked Jun 04 '19 23:06

breiker


1 Answers

I believe there are two things happening here.

Firstly, there is definitely an issue with clang's implementation, so filing a bug report is a good move.

But there's an issue as well when you call foo on a D reference

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

The warning visual studio gives you is actually very accurate: there are technically two options in each call, but one gets chosen over the other through dominance.

For the Dummy1 overload there's the overridden implementation in class B, but also the not-overridden implementation from class C. This is what the warning refers to as the "dominant" one hiding the other one. The dominant version in this case is the overridden version in class B while the weak one is the one from class C. If you'd overwritten the Dummy1 overload in class C as well, you would have had an ambiguous call.

The analogous is true for the Dummy2 overload.

So what the compiler is warning you about is that in the case of a known D instance, you actually have a choice and should be explicit about it.

like image 121
devb Avatar answered Nov 08 '22 13:11

devb