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?


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


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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.