Mehrfachvererbung führt zu einer unechten Mehrdeutigkeit virtueller Funktionen

8

In diesem Beispiel werden Klassen Foound Baraus einer Bibliothek bereitgestellt. Meine Klasse Bazerbt von beiden.

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

Ich erhalte den Kompilierungsfehler, reference to ‘do_stuff’ is ambiguousder mir falsch erscheint, da die beiden Funktionssignaturen völlig unterschiedlich sind. Wenn do_stuffes nicht virtuell wäre, könnte ich anrufen Bar::do_stuff, um es zu disambiguieren, aber dies bricht den Polymorphismus und verursacht einen Linkerfehler.

Kann ich funcden virtuellen Anruf tätigen , do_stuffohne Dinge umzubenennen?

Spraff
quelle
1
Sie müssen eindeutig festlegen, welche Basisklassenfunktion aufgerufen werden soll. So etwas wie diese
AndyG

Antworten:

10

Du kannst das:

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

Getestet mit wandbox gcc spätestens und es kompiliert gut. Ich denke, es ist der gleiche Fall mit Funktionsüberladungen. Wenn Sie eine überladen, können Sie keine Basisklassenimplementierungen ohne verwenden using.

Tatsächlich hat dies nichts mit virtuellen Funktionen zu tun. Das folgende Beispiel hat den gleichen Fehler 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
    }
};

Mögliche verwandte Frage

Raxvan
quelle
2

Namenssuche und Überlastungsauflösung sind unterschiedlich. Der Name muss zuerst in einem Bereich gefunden werden, dh wir finden müssen , Xso dass der Name do_stuffzu aufgelöst wird X::do_stuff- unabhängig von der Nutzung der Namens - und dann Auflösung wählt zwischen den verschiedenen Erklärungen von Überlastung X::do_stuff.

Der Prozess ist nicht alle solche Fälle zu identifizieren A::do_stuff, B::do_stuffusw. , die sichtbar sind, und dann die Überladungsauflösung unter der Vereinigung , dass zuführen. Stattdessen muss ein einzelner Bereich für den Namen identifiziert werden.

In diesem Code:

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

Bazenthält den Namen nicht do_stuff, sodass Basisklassen nachgeschlagen werden können. Der Name kommt jedoch in zwei verschiedenen Basen vor, sodass bei der Namenssuche kein Bereich identifiziert werden kann. Wir kommen nie so weit zur Überlastungsauflösung.

Das vorgeschlagene Update in der anderen Antwort funktioniert, da es den Namen do_stuffin den Bereich von Bazeinführt und außerdem zwei Überladungen für den Namen einführt. Die Namenssuche bestimmt also, dass dies do_stuffbedeutet, Baz::do_stuffund dann wählt die Überlastungsauflösung aus den beiden Funktionen aus, die als bekannt sind Baz::do_stuff.


Abgesehen davon ist das Abschatten eine weitere Folge der Namenssuche (keine Regel an sich). Die Namenssuche wählt den inneren Bereich aus, sodass alles im äußeren Bereich nicht übereinstimmt.

Ein weiterer komplizierender Faktor tritt auf, wenn eine argumentabhängige Suche im Spiel ist. Um es kurz zusammenzufassen: Die Namenssuche wird mehrmals für einen Funktionsaufruf mit Argumenten des Klassentyps durchgeführt - der Basisversion, wie in meiner Antwort beschrieben, und dann erneut für den Typ jedes Arguments. Dann geht die Vereinigung der gefundenen Bereiche in den Überlastsatz. Dies gilt jedoch nicht für Ihr Beispiel, da Ihre Funktion nur Parameter vom integrierten Typ enthält.

MM
quelle