Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple inheritence leads to spurious ambiguous virtual function overload

In this example, classes Foo and Bar are provided from a library. My class Baz inherits from both.

struct Foo
{
    void do_stuff (int, int);
};

struct Bar
{
    virtual void do_stuff (float) = 0;
};

struct Baz : public Foo, public Bar
{
    void func ()
    {
        do_stuff (1.1f); // ERROR HERE
    }
};

struct BazImpl : public Baz
{
    void do_stuff (float) override {};
};

int main ()
{
    BazImpl () .func ();
}

I get the compilation error reference to ‘do_stuff’ is ambiguous which seems spurious to me since the two function signatures are entirely different. If do_stuff was non-virtual I could call Bar::do_stuff to disambiguate it, but to do so breaks polymorphism and causes a linker error.

Can I make func call the virtual do_stuff without renaming things?

like image 936
spraff Avatar asked Nov 04 '19 17:11

spraff


2 Answers

You can do this:

struct Baz : public Foo, public Bar
{
    using Bar::do_stuff;
    using Foo::do_stuff;
    //...
}

Tested with wandbox gcc latest and it compiles fine. I think it's the same case with function overloads, once you overload one you can't use base class implementations without using.

In fact this has nothing to do with virtual functions. The following example has the same error GCC 9.2.0 error: reference to 'do_stuff' is ambiguous:

struct Foo
{
    void do_stuff (int, int){}
};

struct Bar
{
    void do_stuff (float) {}
};

struct Baz : public Foo, public Bar
{
    void func ()
    {
        do_stuff (1.1f); // ERROR HERE
    }
};

Possible related question

like image 114
Raxvan Avatar answered Nov 15 '22 15:11

Raxvan


Name lookup and overload resolution are different. The name must be found in a scope first, i.e. we must find an X so that the name do_stuff is resolved to X::do_stuff -- independently of the usage of the name -- and then overload resolution selects between the different declarations of X::do_stuff.

The process is NOT to identify all such cases A::do_stuff, B::do_stuff, etc. that are visible, and then perform overload resolution amongst the union of that. Instead, a single scope must be identified for the name.

In this code:

struct Baz : public Foo, public Bar
{
    void func ()
    {
        do_stuff (1.1f); // ERROR HERE
    }
};

Baz does not contain the name do_stuff, so base classes can be looked up . But the name occurs in two different bases, so name lookup fails to identify a scope. We never get so far as overload resolution.

The suggested fix in the other answer works because it introduces the name do_stuff to the scope of Baz, and also introduces 2 overloads for the name. So name lookup determines that do_stuff means Baz::do_stuff and then overload resolution selects from the two functions that are known as Baz::do_stuff.


As an aside, shadowing is another consequence of name lookup (not a rule in itself). Name lookup selects the inner scope, and so anything in the outer scope is not a match.

A further complicating factor occurs when argument-dependent lookup is in play. To summarize very briefly, name lookup is done multiple times for a function call with arguments of class type -- the basic version as described in my answer, and then again for each argument's type. Then the union of the scopes found goes into the overload set. But that does not apply to your example since your function only has parameters of built-in type.

like image 40
M.M Avatar answered Nov 15 '22 17:11

M.M