Herança múltipla leva a sobrecarga de função virtual ambígua e espúria

8

Neste exemplo, classes Fooe Barsão fornecidas a partir de uma biblioteca. Minha classe Bazherda de ambos.

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 ();
}

Recebo o erro de compilação reference to ‘do_stuff’ is ambiguousque parece falso para mim, pois as duas assinaturas de funções são totalmente diferentes. Se do_stuffnão fosse virtual, eu poderia ligar Bar::do_stuffpara desambiguá-lo, mas fazer isso quebra o polimorfismo e causa um erro no vinculador.

Posso fazer funcchamadas para o virtual do_stuffsem renomear as coisas?

spraff
fonte
1
Você precisa desambiguar qual função de classe base chamar. Algo parecido com isto
AndyG 04/11/19

Respostas:

10

Você consegue fazer isso:

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

Testado com o wandbox gcc mais recente e compila bem. Eu acho que é o mesmo caso com sobrecargas de função, uma vez que você sobrecarrega um, você não pode usar implementações de classe base sem using.

De fato, isso não tem nada a ver com funções virtuais. O exemplo a seguir tem o mesmo erro 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
    }
};

Possível questão relacionada

Raxvan
fonte
2

A pesquisa de nome e a resolução de sobrecarga são diferentes. O nome deve ser encontrado em um escopo primeiro, ou seja, precisamos encontrar um Xpara que o nome do_stuffseja resolvido para X::do_stuff- independentemente do uso do nome - e, em seguida, a resolução de sobrecarga selecione entre as diferentes declarações de X::do_stuff.

O processo não é identificar todos esses casos A::do_stuff, B::do_stuffetc., que são visíveis, e depois executar a resolução de sobrecarga entre a união do que isso. Em vez disso, um único escopo deve ser identificado para o nome.

Neste código:

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

Baznão contém o nome do_stuff, portanto, as classes base podem ser consultadas. Mas o nome ocorre em duas bases diferentes, portanto, a pesquisa de nome falha ao identificar um escopo. Nunca chegamos a uma resolução de sobrecarga.

A correção sugerida na outra resposta funciona porque introduz o nome do_stuffno escopo de Baze também introduz 2 sobrecargas para o nome. Portanto, a pesquisa de nome determina o que do_stuffsignifica Baz::do_stuffe, em seguida, a resolução de sobrecarga seleciona as duas funções conhecidas como Baz::do_stuff.


Além disso, o sombreamento é outra consequência da pesquisa de nome (não uma regra em si). A pesquisa de nome seleciona o escopo interno e, portanto, qualquer coisa no escopo externo não corresponde.

Um outro fator complicador ocorre quando a pesquisa dependente de argumento está em jogo. Para resumir muito brevemente, a pesquisa de nome é feita várias vezes para uma chamada de função com argumentos do tipo de classe - a versão básica, conforme descrito na minha resposta, e novamente para o tipo de argumento. Em seguida, a união dos escopos encontrados entra no conjunto de sobrecargas. Mas isso não se aplica ao seu exemplo, pois sua função possui apenas parâmetros do tipo interno.

MILÍMETROS
fonte