Pourquoi deux clauses using se résolvant au même type sont-elles considérées comme ambigües dans gcc

32

J'ai deux classes de base avec des clauses using

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


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

Je déclare ensuite une classe

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

le compilateur signale ensuite une référence d'erreur à 'NetworkPacket' est ambigu 'sendNetworkPacket (NetworkPacket & ...'

Désormais, les deux «clauses d'utilisation» se résolvent dans la même classe sous-jacente. Networking: NetworkPacket

et en fait si je remplace la déclaration de méthode par:

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

il compile très bien.

Pourquoi le compilateur traite-t-il chaque clause using comme un type distinct, même si elles pointent toutes les deux vers le même type sous-jacent? Est-ce obligatoire par la norme ou avons-nous un bug de compilation?

Andrew Goedhart
la source
Il semble que le compilateur ne soit pas assez intelligent
idris
Le point étant que le compilateur à ce stade sait juste qu'il existe trois NetworkPacket- dans MultiCmdQueueCallback, dans PlcMsgFactoryImplCallback, dans la mise en réseau. Lequel utiliser doit être spécifié. Et je ne pense pas que la mise virtualen place sera d'une quelconque utilité ici.
theWiseBro
@idris: au lieu de cela, vous vouliez dire que la norme n'est pas assez permissive. les compilateurs ont raison de suivre la norme.
Jarod42
@ Jarod42 Dans la réponse ci-dessous, 'synonyme du type indiqué par type-id', donc s'ils ont le même type-id, il peut être autorisé à utiliser les deux. que ce soit standart ou compilateur, il semble que quelqu'un ne soit pas assez intelligent.
idris
l'un des problèmes de l'héritage multiple
eagle275

Réponses:

28

Avant d'examiner le type résultant d'alias (et l'accessibilité)

on regarde les noms

et en effet,

NetworkPacket pourrait être

  • MultiCmdQueueCallback::NetworkPacket
  • ou PlcMsgFactoryImplCallback::NetworkPacket

Le fait qu'ils indiquent tous les deux Networking::NetworkPacketest sans importance.

Nous effectuons la résolution des prénoms, ce qui entraîne une ambiguïté.

Jarod42
la source
En fait, cela n'est que partiellement vrai Si j'ajoute une utilisation à PlcNetwork: | en utilisant NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; J'obtiens une erreur de compilation car la clause using précédente est privée.
Andrew Goedhart
@AndrewGoedhart Pas une contradiction. La recherche de nom commence d'abord dans sa propre classe. Comme le compilateur y trouve déjà un nom unique, il est satisfait.
Aconcagua
Mon problème ici est pourquoi le nom se propage-t-il d'une clause de dénomination privée dans la classe de base. Si je supprime l'une des déclarations privées, c'est-à-dire que l'une des classes de base a une clause using privée et l'autre aucune, l'erreur passe à «Le paquet réseau ne nomme pas un type»
Andrew Goedhart
1
La recherche de nom @AndrewGoedhart (évidemment) ne tient pas compte de l'accessibilité. Vous obtenez la même erreur si vous rendez l'un public et l'autre privé. C'est la première erreur à découvrir, c'est donc la première erreur à imprimer. Si vous supprimez un alias, le problème d'ambiguïté a disparu, mais celui d'inacessibilité persiste, vous obtenez donc l'erreur suivante imprimée. Soit dit en passant, pas un bon message d'erreur (? MSVC une fois de plus), GCC est plus est plus précis sur: error: [...] is private within this context.
Aconcagua
1
@AndrewGoedhart Tenez compte des éléments suivants: class A { public: void f(char, int) { } private: void f(int, char) { } }; void demo() { A a; a.f('a', 'd'); }- pas la même chose, mais la résolution de surcharge fonctionne de la même manière: tenez compte de toutes les fonctions disponibles, uniquement après avoir sélectionné celle appropriée, tenez compte de l'accessibilité ... Dans un cas donné, vous obtenez également une ambiguïté; si vous modifiez la fonction privat pour accepter deux caractères, elle sera sélectionnée bien que privée - et vous rencontrez l'erreur de compilation suivante.
Aconcagua
14

Vous pouvez simplement résoudre l'ambiguïté en sélectionnant manuellement celle que vous souhaitez utiliser.

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

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

}

Le compilateur recherche uniquement les définitions dans les classes de base. Si le même type et / ou alias est présent dans les deux classes de base, il se plaint simplement de ne pas savoir lequel utiliser. Peu importe que le type résultant soit le même ou non.

Le compilateur recherche uniquement les noms à la première étape, totalement indépendant si ce nom est une fonction, un type, un alias, une méthode ou autre. Si les noms sont ambigus, aucune autre action n'est effectuée à partir du compilateur! Il se plaint simplement du message d'erreur et s'arrête. Il suffit donc de résoudre l'ambiguïté avec l'instruction using donnée.

Klaus
la source
Ayez des doutes sur le libellé. S'il recherche les définitions , ne prendrait-il pas également en compte le type? Ne chercherait-il pas seulement les noms uniquement (et oublierait-il la définition)? Une référence à la norme serait formidable ...
Aconcagua
Ce dernier commentaire explique ce pourquoi correctement. Remplacez le dernier paragraphe par ce commentaire et je voterai;)
Aconcagua
Je ne peux pas accepter - je ne suis pas l'auteur de la question ... Désolé si j'aurais pu vous énerver. J'essaie juste d'améliorer la réponse, car je sentais que cela ne répondait pas à la question centrale de l'AQ avant ...
Aconcagua
@Aconcagua: Ubs, ma faute :-) Merci pour l'amélioration!
Klaus
En fait, cela ne fonctionne pas car les deux clauses d'utilisation sont privées. Si j'ajoute une utilisation à PlcNetwork: | en utilisant NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; J'obtiens une erreur de compilation car la précédente clause using est privée. Soit dit en passant, si je rend la classe de base en utilisant une clause publique et l'autre privée, j'obtiens toujours une erreur d'ambiguïté. J'obtiens des erreurs d'ambiguïté sur les méthodes qui ne sont pas définies dans la classe de base.
Andrew Goedhart
8

De la documentation :

Une déclaration d'alias de type introduit un nom qui peut être utilisé comme synonyme du type dénoté par type-id. Il n'introduit pas de nouveau type et ne peut pas changer la signification d'un nom de type existant.

Bien que ces deux usingclauses représentent le même type, le compilateur a deux choix dans la situation suivante:

void sendNetworkPacket(const NetworkPacket &pdu);

Il peut choisir entre:

  • MultiCmdQueueCallback::NetworkPacket et
  • PlcMsgFactoryImplCallback::NetworkPacket

car il hérite des deux classes MultiCmdQueueCallbacket des PlcMsgFactoryImplCallbackclasses de base. Un résultat de la résolution du nom du compilateur est une erreur d'ambiguïté que vous avez. Pour résoudre ce problème, vous devez explicitement demander au compilateur d'utiliser l'un ou l'autre comme ceci:

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

ou

void sendNetworkPacket(const PlcMsgFactoryImplCallback::NetworkPacket &pdu);
Casse Noisette
la source
Pour être honnête, je ne me sens pas satisfait ... Ils sont tous les deux synonymes pour le même type. Je peux facilement l'avoir class C { void f(uint32_t); }; void C::f(unsigned int) { }(à condition que l'alias corresponde). Alors pourquoi une différence ici? Ils sont toujours du même type, confirmé par votre citation (que je ne considère pas suffisant pour expliquer) ...
Aconcagua
@Aconcagua: L'utilisation du type de base ou de l'alias ne fait jamais de différence. Un alias n'est jamais un nouveau type. Votre observation n'a rien à voir avec l'ambiguïté que vous générez en donnant le même alias dans deux classes de base.
Klaus
1
@Aconcagua Je pense que l'exemple que vous avez mentionné n'est pas le bon équivalent pour la situation de la question
NutCracker
Eh bien, changeons un peu: nommons les classes A, B et C et le typedef D, alors vous pouvez même faire: class C : public A, public B { void f(A::D); }; void C::f(B::D) { }- au moins GCC accepte.
Aconcagua
L'auteur de la question a littéralement demandé: «Pourquoi le compilateur traite-t-il chaque clause using comme un type distinct, même si elles pointent toutes les deux vers le même type sous-jacent? - et je ne vois pas comment la citation clarifierait le pourquoi , au lieu de cela, cela confirme simplement la confusion de l'AQ ... Je ne veux pas dire que la réponse est fausse , mais elle ne clarifie pas suffisamment à mes yeux .. .
Aconcagua
2

Il y a deux erreurs:

  1. Accès aux alias de type privé
  2. Référence ambiguë aux types d'alias

privé-privé

Je ne vois pas de problème que le compilateur se plaint d'abord du deuxième problème parce que l'ordre n'a pas vraiment d'importance - vous devez résoudre les deux problèmes pour continuer.

public-public

Si vous modifiez la visibilité des deux MultiCmdQueueCallback::NetworkPacketet PlcMsgFactoryImplCallback::NetworkPacketen public ou protégé, le deuxième problème (ambiguïté) est évident - ce sont deux alias de type différents bien qu'ils aient le même type de données sous-jacent. Certains peuvent penser qu'un compilateur "intelligent" peut résoudre cela (un cas spécifique) pour vous, mais gardez à l'esprit que le compilateur doit "penser en général" et prendre des décisions basées sur des règles globales au lieu de faire des exceptions spécifiques à la casse. Imaginez le cas suivant:

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


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

Le compilateur doit-il traiter les deux de NetworkPacketIDla même façon? Bien sûr que non. Parce que sur un système 32 bits, size_test 32 bits de long toutuint64_t est toujours de 64 bits. Mais si nous voulons que le compilateur vérifie les types de données sous-jacents, il ne peut pas distinguer ceux sur un système 64 bits.

public-privé

Je crois que cet exemple n'a aucun sens dans le cas d'utilisation d'OP, mais puisque nous résolvons ici des problèmes en général, considérons que:

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

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

Je pense que dans ce cas, le compilateur doit traiter PlcNetwork::NetworkPacketcomme PlcMsgFactoryImplCallback::NetworkPacketparce qu'il n'a pas d'autre choix. Pourquoi il refuse toujours de le faire et blâme l'ambiguïté est un mystère pour moi.

PooSH
la source
"Pourquoi il refuse toujours de le faire et blâme l'ambiguïté est un mystère pour moi." En C ++, la recherche de nom (visibilité) précède la vérification d'accès. IIRC, j'ai lu quelque part que la justification est que changer un nom de privé en public ne devrait pas casser le code existant, mais je ne suis pas tout à fait sûr.
LF