La herencia múltiple conduce a una sobrecarga falsa de funciones virtuales ambigua

8

En este ejemplo, las clases Fooy Barse proporcionan desde una biblioteca. Mi clase Bazhereda 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 ();
}

Me sale el error de compilación reference to ‘do_stuff’ is ambiguousque me parece falso porque las dos firmas de funciones son completamente diferentes. Si do_stuffno fuera virtual, podría llamar Bar::do_stuffpara desambiguarlo, pero al hacerlo se rompe el polimorfismo y se produce un error de enlace.

¿Puedo hacer una funcllamada virtual do_stuffsin renombrar cosas?

rociar
fuente
1
Necesita desambiguar a qué función de clase base llamar. Algo como esto
AndyG

Respuestas:

10

Puedes hacerlo:

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

Probado con wandbox gcc más reciente y se compila bien. Creo que es el mismo caso con sobrecargas de funciones, una vez que sobrecargas una no puedes usar implementaciones de clase base sin ellas using.

De hecho, esto no tiene nada que ver con las funciones virtuales. El siguiente ejemplo tiene el mismo 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
    }
};

Posible pregunta relacionada

Raxvan
fuente
2

La búsqueda de nombres y la resolución de sobrecarga son diferentes. El nombre debe encontrarse primero en un ámbito, es decir, debemos encontrar uno Xpara que do_stuffse resuelva el nombre X::do_stuff, independientemente del uso del nombre, y luego seleccione la resolución de sobrecarga entre las diferentes declaraciones de X::do_stuff.

El proceso NO es identificar todos esos casos A::do_stuff, B::do_stuffetc. que son visibles, y luego realizar una resolución de sobrecarga entre la unión de eso. En cambio, se debe identificar un solo alcance para el nombre.

En este código:

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

Baz no contiene el nombre do_stuff , por lo que se pueden buscar las clases base. Pero el nombre aparece en dos bases diferentes, por lo que la búsqueda de nombres no puede identificar un alcance. Nunca llegamos tan lejos como la resolución de sobrecarga.

La solución sugerida en la otra respuesta funciona porque introduce el nombre do_stuffal alcance de Baz, y también introduce 2 sobrecargas para el nombre. Por lo tanto, la búsqueda de nombres determina ese do_stuffmedio Baz::do_stuffy luego selecciona la resolución de sobrecarga de las dos funciones que se conocen como Baz::do_stuff.


Por otro lado, el sombreado es otra consecuencia de la búsqueda de nombres (no es una regla en sí misma). La búsqueda de nombre selecciona el alcance interno, por lo que cualquier cosa en el alcance externo no coincide.

Otro factor de complicación ocurre cuando la búsqueda dependiente de argumentos está en juego. Para resumir muy brevemente, la búsqueda de nombres se realiza varias veces para una llamada de función con argumentos de tipo de clase: la versión básica como se describe en mi respuesta, y luego nuevamente para cada tipo de argumento. Luego, la unión de los ámbitos encontrados entra en el conjunto de sobrecarga. Pero eso no se aplica a su ejemplo ya que su función solo tiene parámetros de tipo incorporado.

MM
fuente