Warum werden zwei using-Klauseln, die in denselben Typ aufgelöst werden, in gcc als mehrdeutig angesehen?

32

Ich habe zwei Basisklassen mit using-Klauseln

 class MultiCmdQueueCallback {
  using NetworkPacket  = Networking::NetworkPacket;
  ....
 }


 class PlcMsgFactoryImplCallback {
   using NetworkPacket = Networking::NetworkPacket;
  ....
 }

Ich erkläre dann eine Klasse

class PlcNetwork : 
  public RouterCallback, 
  public PlcMsgFactoryImplCallback, 
  public MultiCmdQueueCallback {
  private:
    void sendNetworkPacket(const NetworkPacket &pdu);
}

Der Compiler kennzeichnet dann einen Fehlerverweis auf 'NetworkPacket', der nicht eindeutig ist. 'sendNetworkPacket (NetworkPacket & ...'

Jetzt werden beide 'using-Klauseln' in dieselbe zugrunde liegende Klasse Networking: NetworkPacket aufgelöst

und in der Tat, wenn ich die Methodendeklaration durch Folgendes ersetze:

 void sendNetworkPacket(const Networking::NetworkPacket &pdu);

es kompiliert gut.

Warum behandelt der Compiler jede using-Klausel als einen eigenen Typ, obwohl beide auf denselben zugrunde liegenden Typ verweisen? Ist dies vom Standard vorgeschrieben oder haben wir einen Compiler-Fehler?

Andrew Goedhart
quelle
Es scheint, dass der Compiler nicht klug genug ist
idris
Der Punkt ist, dass der Compiler zu diesem Zeitpunkt nur weiß, dass es drei gibt NetworkPacket- in MultiCmdQueueCallback, in PlcMsgFactoryImplCallback, in Networking. Welches verwendet werden soll, sollte angegeben werden. Und ich denke nicht, dass Putten virtualhier hilfreich sein wird.
theWiseBro
@idris: stattdessen meintest du Standard ist nicht genug freizügig. Compiler folgen zu Recht dem Standard.
Jarod42
@ Jarod42 In der folgenden Antwort antworten Sie auf "Synonym für den mit Typ-ID bezeichneten Typ". Wenn sie also dieselbe Typ-ID haben, können beide verwendet werden. Ob Standard oder Compiler, es scheint nur, dass jemand tatsächlich nicht klug genug ist.
Idris
eines der Probleme der Mehrfachvererbung
eagle275

Antworten:

28

Bevor Sie sich den resultierenden Alias-Typ (und die Barrierefreiheit) ansehen

Wir schauen uns Namen an

und in der Tat,

NetworkPacket könnte sein

  • MultiCmdQueueCallback::NetworkPacket
  • oder PlcMsgFactoryImplCallback::NetworkPacket

Die Tatsache, auf die beide hinweisen, Networking::NetworkPacketist irrelevant.

Wir führen die Auflösung von Vornamen durch, was zu Mehrdeutigkeiten führt.

Jarod42
quelle
Eigentlich ist dies nur teilweise wahr, wenn ich PlcNetwork eine Verwendung hinzufüge: | using NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Ich erhalte einen Compilerfehler, weil die vorherige using-Klausel privat ist.
Andrew Goedhart
@ AndrewGoedhart Kein Widerspruch. Die Namenssuche beginnt zuerst in der eigenen Klasse. Da der Compiler dort dort bereits einen eindeutigen Namen findet, ist er zufrieden.
Aconcagua
Mein Problem hier ist, warum der Name aus einer privaten Namensklausel in der Basisklasse stammt. Wenn ich eine der privaten Deklarationen entferne, dh eine der Basisklassen hat eine private using-Klausel und die andere keine, ändert sich der Fehler in 'Netzwerkpaket benennt keinen Typ'
Andrew Goedhart
1
@AndrewGoedhart Die Namenssuche berücksichtigt (offensichtlich) nicht die Barrierefreiheit. Sie erhalten den gleichen Fehler, wenn Sie eine öffentlich und die andere privat machen. Dies ist der erste Fehler, der entdeckt wird. Dies ist also der erste Fehler, der gedruckt wird. Wenn Sie einen Alias ​​entfernen, ist das Problem der Mehrdeutigkeit behoben, das Problem der Unzugänglichkeit bleibt jedoch bestehen, sodass der nächste Fehler gedruckt wird. Übrigens keine gute Fehlermeldung (MSVC noch einmal?), GCC ist genauer ist über : error: [...] is private within this context.
Aconcagua
1
@AndrewGoedhart Beachten Sie Folgendes: class A { public: void f(char, int) { } private: void f(int, char) { } }; void demo() { A a; a.f('a', 'd'); }- Nicht dasselbe, aber die Überlastungsauflösung funktioniert gleich: Berücksichtigen Sie alle verfügbaren Funktionen, nur nachdem Sie die entsprechende ausgewählt haben, berücksichtigen Sie die Zugänglichkeit ... In einem bestimmten Fall erhalten Sie auch Mehrdeutigkeiten. Wenn Sie die private Funktion so ändern, dass zwei Zeichen akzeptiert werden, wird sie ausgewählt, obwohl sie privat ist - und Sie stoßen auf den nächsten Kompilierungsfehler.
Aconcagua
14

Sie können die Mehrdeutigkeit einfach beheben, indem Sie manuell auswählen, welche Sie verwenden möchten.

class PlcNetwork : 
  public RouterCallback, 
  public PlcMsgFactoryImplCallback, 
  public MultiCmdQueueCallback {

using NetworkPacket= PlcMsgFactoryImplCallback::NetworkPacket; // <<< add this line
private:
    void sendNetworkPacket(const NetworkPacket &pdu);

}

Der Compiler sucht nur nach den Definitionen in den Basisklassen. Wenn in beiden Basisklassen der gleiche Typ und / oder Alias ​​vorhanden ist, wird lediglich beanstandet, dass nicht bekannt ist, welcher Typ verwendet werden soll. Es spielt keine Rolle, ob der resultierende Typ der gleiche ist oder nicht.

Der Compiler sucht nur im ersten Schritt nach Namen, völlig unabhängig, ob dieser Name eine Funktion, ein Typ, ein Alias, eine Methode oder was auch immer ist. Wenn die Namen nicht eindeutig sind, wird vom Compiler keine weitere Aktion ausgeführt! Es beschwert sich einfach mit der Fehlermeldung und stoppt. Lösen Sie also einfach die Mehrdeutigkeit mit der angegebenen using-Anweisung.

Klaus
quelle
Haben Sie einige Zweifel an der Formulierung. Wenn es die Definitionen nachschlägt , würde es dann nicht auch den Typ berücksichtigen? Würde es nicht nur Namen nachschlagen (und vergessen, wie definiert)? Ein Hinweis auf den Standard wäre großartig ...
Aconcagua
Dieser letzte Kommentar erklärt das Warum richtig. Ersetzen Sie den letzten Absatz durch diesen Kommentar und ich stimme zu;)
Aconcagua
Ich kann nicht akzeptieren - ich bin nicht der Fragesteller ... Entschuldigung, wenn ich Ihnen auf die Nerven gegangen bin. Ich habe nur versucht, die Antwort zu verbessern, da ich der Meinung war, dass sie die Kernfrage der Qualitätssicherung vorher nicht beantwortet hat ...
Aconcagua
@Aconcagua: Ubs, meine Schuld :-) Danke für die Verbesserung!
Klaus
Eigentlich funktioniert das nicht, weil beide using-Klauseln privat sind. Wenn ich PlcNetwork eine Verwendung hinzufüge: | using NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Ich erhalte einen Compilerfehler, weil die vorherige using-Klausel privat ist. Übrigens, wenn ich die eine Basisklasse mit Klausel öffentlich und die andere privat mache, erhalte ich immer noch einen Mehrdeutigkeitsfehler. Ich erhalte Mehrdeutigkeitsfehler bei Methoden, die nicht in der Basisklasse definiert sind.
Andrew Goedhart
8

Aus den Dokumenten :

Eine Typ-Alias-Deklaration führt einen Namen ein, der als Synonym für den mit Typ-ID bezeichneten Typ verwendet werden kann. Es wird kein neuer Typ eingeführt und die Bedeutung eines vorhandenen Typnamens kann nicht geändert werden.

Obwohl diese beiden usingKlauseln denselben Typ darstellen, hat der Compiler in der folgenden Situation zwei Möglichkeiten:

void sendNetworkPacket(const NetworkPacket &pdu);

Es kann wählen zwischen:

  • MultiCmdQueueCallback::NetworkPacket und
  • PlcMsgFactoryImplCallback::NetworkPacket

weil es von beiden MultiCmdQueueCallbackund PlcMsgFactoryImplCallbackBasisklassen erbt . Ein Ergebnis der Namensauflösung des Compilers ist ein Mehrdeutigkeitsfehler, den Sie haben. Um dies zu beheben, müssen Sie den Compiler explizit anweisen, das eine oder andere wie folgt zu verwenden:

void sendNetworkPacket(const MultiCmdQueueCallback::NetworkPacket &pdu);

oder

void sendNetworkPacket(const PlcMsgFactoryImplCallback::NetworkPacket &pdu);
Nussknacker
quelle
Um ehrlich zu sein, bin ich nicht zufrieden ... Beide sind Synonyme für den gleichen Typ. Ich kann leicht haben class C { void f(uint32_t); }; void C::f(unsigned int) { }(vorausgesetzt, der Alias ​​stimmt überein). Warum also ein Unterschied hier? Sie sind immer noch vom gleichen Typ, bestätigt durch Ihr Zitat (das ich nicht für ausreichend halte, um es zu erklären) ...
Aconcagua
@Aconcagua: Die Verwendung des Basistyps oder des Alias ​​macht keinen Unterschied. Ein Alias ​​ist niemals ein neuer Typ. Ihre Beobachtung hat nichts mit der Mehrdeutigkeit zu tun, die Sie durch die Angabe des gleichen Alias ​​in zwei Basisklassen erzeugen.
Klaus
1
@Aconcagua Ich denke, das von Ihnen erwähnte Beispiel ist nicht das richtige Äquivalent für die Situation aus der Frage
NutCracker
Nun, lassen Sie uns ein wenig ändern: Nennen wir die Klassen A, B und C und das typedef D, dann können Sie sogar Folgendes tun: class C : public A, public B { void f(A::D); }; void C::f(B::D) { }- Zumindest akzeptiert GCC.
Aconcagua
Der Autor der Frage fragte buchstäblich: "Warum behandelt der Compiler jede using-Klausel als einen eigenen Typ, obwohl beide auf denselben zugrunde liegenden Typ verweisen?" - und ich sehe nicht, wie das Zitat das Warum klarstellen würde , stattdessen bestätigt es nur die Verwirrung der Qualitätssicherung ... Ich möchte nicht sagen, dass die Antwort falsch ist , aber es klärt in meinen Augen nicht ausreichend. .
Aconcagua
2

Es gibt zwei Fehler:

  1. Zugriff auf Aliase vom Typ Private
  2. Mehrdeutiger Verweis auf Typ-Aliase

privat-privat

Ich sehe kein Problem, dass sich der Compiler zuerst über das zweite Problem beschwert, da die Reihenfolge nicht wirklich wichtig ist - Sie müssen beide Probleme beheben, um fortzufahren.

öffentlich-öffentlich

Wenn Sie die Sichtbarkeit von beiden ändern MultiCmdQueueCallback::NetworkPacketund PlcMsgFactoryImplCallback::NetworkPacketentweder öffentlich oder geschützt, ist das zweite Problem (Mehrdeutigkeit) offensichtlich - dies sind zwei verschiedene Typ-Aliase, obwohl sie denselben zugrunde liegenden Datentyp haben. Einige mögen denken, dass ein "kluger" Compiler dies (einen bestimmten Fall) für Sie lösen kann, aber denken Sie daran, dass der Compiler "allgemein denken" und Entscheidungen auf der Grundlage globaler Regeln treffen muss, anstatt fallspezifische Ausnahmen zu treffen. Stellen Sie sich folgenden Fall vor:

class MultiCmdQueueCallback {
    using NetworkPacketID  = size_t;
    // ...
};


class PlcMsgFactoryImplCallback {
    using NetworkPacketID = uint64_t;
    // ...
};

Sollte der Compiler beide NetworkPacketIDgleich behandeln? Sicher nicht. Denn auf einem 32-Bit-System size_tist es 32-Bit lang, während uint64_tes immer 64-Bit ist. Wenn der Compiler jedoch nach zugrunde liegenden Datentypen suchen soll, kann er diese auf einem 64-Bit-System nicht unterscheiden.

öffentlich-privat

Ich glaube, dieses Beispiel macht im Anwendungsfall von OP keinen Sinn, aber da wir hier allgemein Probleme lösen, betrachten wir Folgendes:

class MultiCmdQueueCallback {
private:
    using NetworkPacket  = Networking::NetworkPacket;
    // ...
};

class PlcMsgFactoryImplCallback {
public:
    using NetworkPacket  = Networking::NetworkPacket;
    // ...
};

Ich denke in diesem Fall sollte der Compiler so behandeln, PlcNetwork::NetworkPacketals hätte PlcMsgFactoryImplCallback::NetworkPacketer keine anderen Möglichkeiten. Warum es sich immer noch weigert und die Mehrdeutigkeit beschuldigt, ist mir ein Rätsel.

PooSH
quelle
"Warum es sich immer noch weigert und die Mehrdeutigkeit beschuldigt, ist mir ein Rätsel." In C ++ geht die Namenssuche (Sichtbarkeit) der Zugriffsprüfung voraus. IIRC, ich habe irgendwo gelesen, dass das Grundprinzip darin besteht, dass das Ändern eines Namens von privat zu öffentlich den vorhandenen Code nicht beschädigen sollte, aber ich bin mir nicht ganz sicher.
LF