Perché due clausole using si risolvono nello stesso tipo visto come ambiguo in gcc


32

Ho due classi base con l'utilizzo di clausole

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


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

Dichiaro quindi una classe

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

il compilatore quindi contrassegna un riferimento di errore a "NetworkPacket" è ambiguo "sendNetworkPacket (NetworkPacket & ..."

Ora entrambe le "clausole using" si risolvono nella stessa classe sottostante Networking: NetworkPacket

e infatti se sostituisco la dichiarazione del metodo con:

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

si compila bene.

Perché il compilatore tratta ciascuna clausola using come un tipo distinto anche se entrambi indicano lo stesso tipo sottostante. Questo è obbligatorio per lo standard o abbiamo un bug del compilatore?


Sembra che il compilatore non sia abbastanza intelligente
idris,

Il punto è che il compilatore a questo punto sa solo che ne esistono tre NetworkPacket: in MultiCmdQueueCallback, in PlcMsgFactoryImplCallback, in Networking. Quale usare dovrebbe essere specificato. E non credo che mettere virtualqui sarà di alcun aiuto.
theWiseBro

@idris: invece, intendevi che lo standard non è abbastanza permissivo. i compilatori hanno ragione a seguire lo standard.
Jarod42,

@ Jarod42 Nella seguente risposta "sinonimo del tipo indicato da ID-tipo", quindi se hanno lo stesso ID-tipo, è possibile utilizzare entrambi. sia standart che compilatore, sembra che qualcuno non sia abbastanza intelligente.
idris,

uno dei problemi dell'ereditarietà
multipla

Risposte:


28

Prima di esaminare l'alias risultante, tipo (e accessibilità)

guardiamo i nomi

e senza dubbio,

NetworkPacket potrebbe essere

  • MultiCmdQueueCallback::NetworkPacket
  • o PlcMsgFactoryImplCallback::NetworkPacket

Il fatto che entrambi indicano Networking::NetworkPacketè irrilevante.

Facciamo la risoluzione del nome, che si traduce in ambiguità.


In realtà questo è solo parzialmente vero se aggiungo un utilizzo a PlcNetwork: | utilizzando NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Ottengo un errore del compilatore perché la precedente clausola using è privata.
Andrew Goedhart,

@AndrewGoedhart Non è una contraddizione. La ricerca del nome inizia prima nella propria classe. Dato che il compilatore trova già lì un nome univoco, è soddisfatto.
Aconcagua,

Il mio problema qui è il motivo per cui il nome viene propagato da una clausola di denominazione privata nella classe base. Se rimuovo una delle dichiarazioni private, ovvero una delle classi di base ha una clausola using privata e l'altra nessuna, l'errore cambia in "Il pacchetto di rete non nomina un tipo"
Andrew Goedhart,

1
@AndrewGoedhart La ricerca del nome (ovviamente) non considera l'accessibilità. Si ottiene lo stesso errore se si rende uno pubblico e l'altro privato. Questo è il primo errore da scoprire, quindi è il primo errore da stampare. Se rimuovi un alias, il problema dell'ambiguità scompare, ma rimane quello dell'inaccessibilità, in modo da ottenere il prossimo errore stampato. Tra l'altro, non è un messaggio di errore buona (? MSVC ancora una volta), GCC è più è più preciso riguardo: error: [...] is private within this context.
Aconcagua,

1
@AndrewGoedhart Considera quanto segue: class A { public: void f(char, int) { } private: void f(int, char) { } }; void demo() { A a; a.f('a', 'd'); }- non è lo stesso, ma la risoluzione del sovraccarico funziona allo stesso modo: considera tutte le funzioni disponibili, solo dopo aver selezionato quella appropriata considera l'accessibilità ... In un determinato caso, ottieni anche ambiguità; se si modifica la funzione privat per accettare due caratteri, verrà selezionata anche se privata - e si verifica l'errore di compilazione successivo.
Aconcagua,

14

Puoi semplicemente risolvere l'ambiguità selezionando manualmente quale vuoi usare.

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

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

}

Il compilatore cerca solo le definizioni nelle classi base. Se lo stesso tipo e / o alias è presente in entrambe le classi di base, si lamenta semplicemente di non sapere quale usare. Non importa se il tipo risultante è uguale o no.

Il compilatore cerca solo i nomi nel primo passaggio, completamente indipendente se questo nome è una funzione, un tipo, un alias, un metodo o altro. Se i nomi sono ambigui, non viene eseguita alcuna ulteriore azione dal compilatore! Si lamenta semplicemente con il messaggio di errore e si ferma. Quindi, risolvi semplicemente l'ambiguità con l'istruzione using fornita.


Hai dei dubbi sulla formulazione. Se cerca le definizioni , non prenderebbe in considerazione anche il tipo? Non cercherebbe solo i nomi (e dimenticherà quanto definito)? Qualche riferimento allo standard sarebbe fantastico ...
Aconcagua,

Quest'ultimo commento è ciò che spiega il perché correttamente. Sostituisci l'ultimo paragrafo con questo commento e voterò;)
Aconcagua,

Non posso accettare - Non sono l'autore della domanda ... Scusate se potrei essermi innervosito. Stavo solo cercando di migliorare la risposta, poiché pensavo che non avesse risposto alla domanda principale del QA prima ...
Aconcagua,

@Aconcagua: Ubs, colpa mia :-) Grazie per il miglioramento!
Klaus,

In realtà questo non funziona perché entrambe le clausole using sono private. Se aggiungo un utilizzo a PlcNetwork: | utilizzando NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Ottengo un errore del compilatore perché la precedente clausola using è privata. A proposito, se creo una classe base usando la clausola pubblica e l'altra privata, ottengo ancora un errore di ambiguità. Ottengo errori di ambiguità su metodi che non sono definiti nella classe base.
Andrew Goedhart,

8

Dai documenti :

Una dichiarazione di tipo alias introduce un nome che può essere utilizzato come sinonimo del tipo indicato da id-tipo. Non introduce un nuovo tipo e non può cambiare il significato di un nome di tipo esistente.

Sebbene, queste due usingclausole rappresentino lo stesso tipo, il compilatore ha due scelte nella seguente situazione:

void sendNetworkPacket(const NetworkPacket &pdu);

Può scegliere tra:

  • MultiCmdQueueCallback::NetworkPacket e
  • PlcMsgFactoryImplCallback::NetworkPacket

perché eredita da entrambe MultiCmdQueueCallbacke PlcMsgFactoryImplCallbackclassi di base. Un risultato della risoluzione dei nomi del compilatore è l'errore di ambiguità che hai. Per risolvere questo problema, devi indicare esplicitamente al compilatore di utilizzare l'uno o l'altro come questo:

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

o

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

Ad essere sincero, non mi sento soddisfatto ... Sono entrambi sinonimi dello stesso tipo. Posso facilmente averlo class C { void f(uint32_t); }; void C::f(unsigned int) { }(purché corrisponda all'alias). Quindi perché una differenza qui? Sono ancora dello stesso tipo, confermati dalla tua citazione (che non considero sufficiente da spiegare) ...
Aconcagua,

@Aconcagua: l'utilizzo del tipo di base o dell'alias non fa mai la differenza. Un alias non è mai un nuovo tipo. La tua osservazione non ha nulla a che fare con l'ambiguità che generi dando lo stesso SAME in due classi base.
Klaus,

1
@Aconcagua Penso che l'esempio che hai citato non sia l'equivalente giusto per la situazione dalla domanda
NutCracker

Bene, cambiamo un po ': diamo un nome alle classi A, B e C e al typedef D, quindi puoi anche fare: class C : public A, public B { void f(A::D); }; void C::f(B::D) { }- almeno GCC accetta.
Aconcagua,

Domanda dell'autore alla lettera "Perché il compilatore tratta ciascuna clausola using come un tipo distinto anche se entrambi indicano lo stesso tipo sottostante?" - e non vedo come la citazione chiarirebbe il perché , invece, conferma semplicemente la confusione del QA ... Non voglio dire che la risposta è sbagliata , ma non chiarisce sufficientemente ai miei occhi .. .
Aconcagua

2

Ci sono due errori:

  1. Accesso ad alias di tipo privato
  2. Riferimento ambiguo alias di tipo

privato-privato

Non vedo un problema che il compilatore si lamenta prima del secondo problema perché l'ordine non ha molta importanza: per risolvere è necessario risolvere entrambi i problemi.

pubblico-pubblico

Se si modifica la visibilità di entrambi MultiCmdQueueCallback::NetworkPackete PlcMsgFactoryImplCallback::NetworkPacketsu pubblica o protetta, il secondo problema (ambiguità) è ovvio: si tratta di due alias di tipo diverso sebbene abbiano lo stesso tipo di dati sottostante. Alcuni potrebbero pensare che un compilatore "intelligente" possa risolvere questo (un caso specifico) per te, ma tieni presente che il compilatore deve "pensare in generale" e prendere decisioni basate su regole globali invece di fare eccezioni specifiche del caso. Immagina il seguente caso:

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


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

Il compilatore dovrebbe trattare entrambi NetworkPacketIDallo stesso modo? Sicuramente no. Perché su un sistema a 32 bit, size_tè lungo a 32 bit mentre uint64_tè sempre a 64 bit. Ma se vogliamo che il compilatore controlli i tipi di dati sottostanti, non potrebbe distinguerli su un sistema a 64 bit.

pubblico privato

Credo che questo esempio non abbia alcun senso nel caso d'uso di OP, ma dato che qui stiamo risolvendo problemi in generale, consideriamo che:

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

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

Penso che in questo caso il compilatore dovrebbe trattare PlcNetwork::NetworkPacketcome PlcMsgFactoryImplCallback::NetworkPacketperché non ha altre scelte. Perché rifiuta ancora di farlo e incolpa l'ambiguità per me è un mistero.


"Perché rifiuta ancora di farlo e incolpa l'ambiguità per me è un mistero." In C ++, la ricerca del nome (visibilità) precede il controllo dell'accesso. IIRC, ho letto da qualche parte che la logica è che cambiare un nome da privato a pubblico non dovrebbe violare il codice esistente, ma non ne sono del tutto sicuro.
LF
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.