¿Por qué dos cláusulas de uso que se resuelven en el mismo tipo se consideran ambiguas en gcc

32

Tengo dos clases base con cláusulas de uso

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


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

Entonces declaro una clase

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

el compilador marca una referencia de error a 'NetworkPacket' es ambiguo 'sendNetworkPacket (NetworkPacket & ...'

Ahora ambas 'cláusulas de uso' se resuelven en la misma clase subyacente Redes: NetworkPacket

y de hecho si reemplazo la declaración del método con:

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

Se compila bien.

¿Por qué el compilador trata cada cláusula de uso como un tipo distinto a pesar de que ambos apuntan al mismo tipo subyacente? ¿Es obligatorio por el estándar o tenemos un error de compilación?

Andrew Goedhart
fuente
Parece que el compilador no es lo suficientemente inteligente
idris
El punto es que el compilador en este punto solo sabe que existen tres NetworkPacket: en MultiCmdQueueCallback, en PlcMsgFactoryImplCallback, en Networking. Cuál debe usarse debe especificarse. Y no creo que poner virtualsea ​​de ninguna ayuda aquí.
theWiseBro
@idris: en cambio, querías decir que el estándar no es lo suficientemente permisivo. los compiladores tienen razón al seguir el estándar.
Jarod42
@ Jarod42 En la respuesta a continuación, 'sinónimo para el tipo denotado por type-id', por lo que si tienen el mismo type-id se les puede permitir usar ambos. ya sea estándar o compilador, parece que alguien realmente no es lo suficientemente inteligente.
idris
uno de los problemas de la herencia múltiple
eagle275

Respuestas:

28

Antes de ver el tipo de alias resultante, (y accesibilidad)

nos fijamos en los nombres

y de hecho,

NetworkPacket puede ser

  • MultiCmdQueueCallback::NetworkPacket
  • o PlcMsgFactoryImplCallback::NetworkPacket

El hecho de que ambos apuntan Networking::NetworkPacketes irrelevante.

Hacemos la resolución del primer nombre, lo que resulta en ambigüedad.

Jarod42
fuente
En realidad, esto solo es parcialmente cierto si agrego un uso a PlcNetwork: | utilizando NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Me sale un error del compilador porque la cláusula anterior de uso es privada.
Andrew Goedhart
@AndrewGoedhart No es una contradicción. La búsqueda de nombres comienza en la propia clase primero. Como el compilador ya encuentra un nombre único allí, está satisfecho.
Aconcagua
Mi problema aquí es por qué el nombre se propaga a partir de una cláusula de nomenclatura privada en la clase base. Si elimino una de las declaraciones privadas, es decir, una de las clases base tiene una cláusula de uso privado y la otra ninguna, el error cambia a 'El paquete de red no nombra un tipo'
Andrew Goedhart
1
La búsqueda de nombre de @AndrewGoedhart (obviamente) no considera la accesibilidad. Obtiene el mismo error si hace que uno sea público y el otro privado. Ese es el primer error que se descubre, por lo que es el primer error que se imprime. Si elimina un alias, el problema de la ambigüedad desaparece, pero el de la inaccesibilidad permanece, por lo que se imprime el siguiente error. Por cierto, no es un buen mensaje de error (? MSVC una vez más), GCC es más precisa más: error: [...] is private within this context.
Aconcagua
1
@AndrewGoedhart Considere lo siguiente: class A { public: void f(char, int) { } private: void f(int, char) { } }; void demo() { A a; a.f('a', 'd'); }- no es lo mismo, pero la resolución de sobrecarga funciona igual: considere todas las funciones disponibles, solo después de seleccionar la apropiada considere la accesibilidad ... En el caso dado, también obtiene ambigüedad; si cambia la función privat para aceptar dos caracteres, se seleccionará aunque sea privada y se encontrará con el siguiente error de compilación.
Aconcagua
14

Simplemente puede resolver la ambigüedad seleccionando manualmente cuál desea usar.

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

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

}

El compilador solo busca las definiciones en las clases base. Si el mismo tipo y / o alias está presente en ambas clases base, simplemente se queja de que no sabe cuál usar. No importa si el tipo resultante es el mismo o no.

El compilador solo busca nombres en el primer paso, totalmente independientes si este nombre es una función, tipo, alias, método o lo que sea. Si los nombres son ambiguos, ¡no se realiza ninguna otra acción desde el compilador! Simplemente se queja con el mensaje de error y se detiene. Entonces, simplemente resuelva la ambigüedad con la declaración de uso dada.

Klaus
fuente
Tiene algunas dudas sobre la redacción. Si busca las definiciones , ¿no consideraría el tipo también? ¿No buscaría solo nombres (y olvidaría cómo se define)? Alguna referencia al estándar sería genial ...
Aconcagua
Este último comentario es lo que explica el por qué correctamente. Reemplace el último párrafo con este comentario y votaré;)
Aconcagua
No puedo aceptar, no soy el autor de la pregunta ... Perdón si te puse nervioso. Solo trato de mejorar la respuesta, ya que sentí que no respondía la pregunta central del control de calidad antes ...
Aconcagua
@Aconcagua: Ubs, mi culpa :-) ¡Gracias por mejorar!
Klaus
En realidad, esto no funciona porque ambas cláusulas que usan son privadas. Si agrego un uso a PlcNetwork: | utilizando NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Me sale un error del compilador porque la cláusula anterior de uso es privada. Por cierto, si hago que una clase base usando la cláusula pública y la otra privada, sigo teniendo un error de ambigüedad. Recibo errores de ambigüedad en los métodos que no están definidos en la clase base.
Andrew Goedhart
8

De los documentos :

Una declaración de alias de tipo introduce un nombre que se puede usar como sinónimo del tipo denotado por type-id. No introduce un nuevo tipo y no puede cambiar el significado de un nombre de tipo existente.

Aunque esas dos usingcláusulas representan el mismo tipo, el compilador tiene dos opciones en la siguiente situación:

void sendNetworkPacket(const NetworkPacket &pdu);

Puede elegir entre:

  • MultiCmdQueueCallback::NetworkPacket y
  • PlcMsgFactoryImplCallback::NetworkPacket

porque hereda de ambas MultiCmdQueueCallbacky de PlcMsgFactoryImplCallbacklas clases base. Un resultado de la resolución del nombre del compilador es el error de ambigüedad que tiene. Para solucionar esto, debe indicar explícitamente al compilador que use uno u otro de esta manera:

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

o

void sendNetworkPacket(const PlcMsgFactoryImplCallback::NetworkPacket &pdu);
Cascanueces
fuente
Para ser honesto, no me siento satisfecho ... Ambos son sinónimos del mismo tipo. Puedo tener fácilmente class C { void f(uint32_t); }; void C::f(unsigned int) { }(siempre que el alias coincida). Entonces, ¿por qué hay una diferencia aquí? Todavía son del mismo tipo, confirmado por su cita (que no considero suficiente para explicar) ...
Aconcagua
@Aconcagua: Usar el tipo base o el alias uno nunca hace la diferencia. Un alias nunca es un tipo nuevo. Su observación no tiene nada que ver con la ambigüedad que genera al dar el MISMO alias en dos clases base.
Klaus
1
@Aconcagua Creo que el ejemplo que mencionaste no es el equivalente correcto para la situación de la pregunta
NutCracker
Bueno, cambiemos un poco: nombremos las clases A, B y C y el typedef D, incluso puede hacer: class C : public A, public B { void f(A::D); }; void C::f(B::D) { }- al menos GCC acepta.
Aconcagua
El autor de la pregunta literalmente preguntó: "¿Por qué el compilador trata cada cláusula de uso como un tipo distinto a pesar de que ambos apuntan al mismo tipo subyacente?" - y no veo cómo la cita aclararía el por qué , en cambio, solo confirma la confusión del control de calidad ... No quiero decir que la respuesta sea incorrecta , pero no aclara lo suficiente en mis ojos ... .
Aconcagua
2

Hay dos errores:

  1. Acceso a alias de tipo privado
  2. Referencia ambigua a los alias de tipo

privado-privado

No veo un problema de que el compilador se queje del segundo problema primero porque el orden realmente no importa: debe solucionar ambos problemas para continuar.

público-público

Si cambia la visibilidad de ambos, MultiCmdQueueCallback::NetworkPacketya PlcMsgFactoryImplCallback::NetworkPacketsea ​​pública o protegida, entonces el segundo problema (ambigüedad) es obvio: esos son dos alias de tipo diferente, aunque tienen el mismo tipo de datos subyacente. Algunos pueden pensar que un compilador "inteligente" puede resolver esto (un caso específico) por usted, pero tenga en cuenta que el compilador necesita "pensar en general" y tomar decisiones basadas en reglas globales en lugar de hacer excepciones específicas de cada caso. Imagine el siguiente caso:

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


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

¿Debería el compilador tratar a ambos por NetworkPacketIDigual? Seguro que no. Porque en un sistema de 32 bits, size_ttiene 32 bits de largo mientras uint64_tque siempre es de 64 bits. Pero si queremos que el compilador verifique los tipos de datos subyacentes, entonces no podría distinguirlos en un sistema de 64 bits.

publico privado

Creo que este ejemplo no tiene ningún sentido en el caso de uso de OP, pero dado que aquí estamos resolviendo problemas en general, consideremos que:

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

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

Creo que en este caso el compilador debe tratar PlcNetwork::NetworkPacketcomo PlcMsgFactoryImplCallback::NetworkPacketporque no tiene otros Oferta de. Por qué todavía se niega a hacerlo y culpa a la ambigüedad es un misterio para mí.

PooSH
fuente
"Por qué todavía se niega a hacerlo y culpa a la ambigüedad es un misterio para mí". En C ++, la búsqueda de nombre (visibilidad) precede a la verificación de acceso. IIRC, leí en alguna parte que la razón es que cambiar un nombre de privado a público no debería romper el código existente, pero no estoy completamente seguro.
LF