Por que duas cláusulas de uso que estão sendo resolvidas para o mesmo tipo são vistas como ambíguas no gcc

32

Eu tenho duas classes base com cláusulas de uso

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


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

Declaro uma classe

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

o compilador sinaliza uma referência de erro para 'NetworkPacket' é ambígua 'sendNetworkPacket (NetworkPacket & ...'

Agora, ambas as 'cláusulas de uso' resolvem a mesma classe subjacente Rede: NetworkPacket

e, de fato, se eu substituir a declaração do método por:

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

compila bem.

Por que o compilador trata cada cláusula de uso como um tipo distinto, embora ambos apontem para o mesmo tipo subjacente. Isso é obrigatório pelo padrão ou temos um bug do compilador?

Andrew Goedhart
fonte
Parece compilador não é inteligente o suficiente
idris 12/02
O ponto é que nesse momento o compilador apenas sabe que existem três NetworkPacket- no MultiCmdQueueCallback, em PlcMsgFactoryImplCallback, em Rede. Qual usar deve ser especificado. E eu não acho que colocar virtualaqui será de alguma ajuda.
theWiseBro
@idris: em vez disso, você quis dizer que o padrão não é permissivo o suficiente. Os compiladores têm razão em seguir o padrão.
Jarod42
@ Jarod42 Na resposta abaixo, responda 'sinônimo do tipo indicado pelo tipo-id'; portanto, se eles tiverem o mesmo tipo-ID, poderá usar os dois. seja standart ou compilador, parece que alguém não é suficientemente inteligente.
idris 12/02
um dos problemas da herança múltipla
eagle275

Respostas:

28

Antes de analisar o tipo resultante de alias, (e acessibilidade)

olhamos para nomes

e realmente,

NetworkPacket pode ser

  • MultiCmdQueueCallback::NetworkPacket
  • ou PlcMsgFactoryImplCallback::NetworkPacket

O fato de ambos apontarem Networking::NetworkPacketé irrelevante.

Nós fazemos a resolução do primeiro nome, o que resulta em ambiguidade.

Jarod42
fonte
Na verdade, isso é apenas parcialmente verdadeiro Se eu adicionar um usando ao PlcNetwork: | using NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Eu recebo um erro do compilador porque a cláusula using anterior é privada.
Andrew Goedhart
@AndrewGoedhart Não é uma contradição. A pesquisa de nome começa na própria classe primeiro. Como o compilador já encontra um nome exclusivo, ele é satisfeito.
Aconcagua
Meu problema aqui é por que o nome é propagado por uma cláusula de nomenclatura privada na classe base. Se eu remover uma das declarações privadas, ou seja, uma das classes base possui uma cláusula private using e a outra nenhuma, o erro muda para 'Network Packet não nomeia um tipo'
Andrew Goedhart
11
@AndrewGoedhart A pesquisa de nome (obviamente) não considera acessibilidade. Você recebe o mesmo erro se tornar um público e o outro privado. Esse é o primeiro erro a ser descoberto e, portanto, o primeiro erro a ser impresso. Se você remover um alias, o problema de ambiguidade desaparecerá, mas o de inacessibilidade permanecerá, para que você imprima o próximo erro. By the way, não é uma boa mensagem de erro (? MSVC mais uma vez), GCC é mais é mais precisas sobre: error: [...] is private within this context.
Aconcagua
11
@AndrewGoedhart Considere o seguinte: class A { public: void f(char, int) { } private: void f(int, char) { } }; void demo() { A a; a.f('a', 'd'); }- não é o mesmo, mas a resolução de sobrecarga funciona da mesma forma: considere todas as funções disponíveis; somente depois de selecionar a apropriada, considere a acessibilidade ... Nesse caso, você também obtém ambiguidade; se você alterar a função privat para aceitar dois caracteres, ela será selecionada, embora seja privada - e você encontrará o próximo erro de compilação.
Aconcagua
14

Você pode resolver a ambiguidade simplesmente selecionando manualmente qual deseja usar.

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

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

}

O compilador procura apenas as definições nas classes base. Se o mesmo tipo e ou alias estiver presente nas duas classes base, ele simplesmente reclama que não sabe qual usar. Não importa se o tipo resultante é o mesmo ou não.

O compilador procura apenas nomes na primeira etapa, totalmente independente se esse nome for uma função, tipo, alias, método ou qualquer outra coisa. Se os nomes forem ambíguos, nenhuma ação adicional será feita a partir do compilador! Simplesmente reclama com a mensagem de erro e para. Portanto, basta resolver a ambiguidade com a instrução using fornecida.

Klaus
fonte
Tem algumas dúvidas sobre o texto. Se procurar as definições , também não consideraria o tipo? Não seria apenas procurar nomes apenas (e esquecer como definido)? Alguma referência ao padrão seria ótima ...
Aconcagua
Este último comentário é o que explica o porquê corretamente. Substitua o último parágrafo por este comentário e vou votar;)
Aconcagua
Não posso aceitar - não sou o autor da pergunta ... Desculpe se posso ter me irritado. Apenas tentando melhorar a resposta, como eu senti que não respondeu à pergunta do núcleo da QA antes ...
Aconcagua
@Aconcagua: Ubs, minha culpa :-) Obrigado por melhorar!
Klaus
Na verdade, isso não funciona porque ambas as cláusulas de uso são privadas. Se eu adicionar um usando ao PlcNetwork: | using NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; Eu recebo um erro do compilador porque a cláusula using anterior é privada. A propósito, se eu criar uma classe base usando a cláusula public e a outra private, ainda recebo um erro de ambiguidade. Eu recebo erros de ambiguidade em métodos que não são definidos na classe base.
Andrew Goedhart
8

Dos documentos :

Uma declaração de alias de tipo introduz um nome que pode ser usado como sinônimo para o tipo indicado por id de tipo. Ele não introduz um novo tipo e não pode alterar o significado de um nome de tipo existente.

Embora essas duas usingcláusulas representem o mesmo tipo, o compilador tem duas opções na seguinte situação:

void sendNetworkPacket(const NetworkPacket &pdu);

Pode escolher entre:

  • MultiCmdQueueCallback::NetworkPacket e
  • PlcMsgFactoryImplCallback::NetworkPacket

porque herda das classes base MultiCmdQueueCallbacke PlcMsgFactoryImplCallback. Um resultado da resolução de nomes do compilador é o erro de ambiguidade que você possui. Para corrigir isso, você precisa instruir explicitamente o compilador a usar um ou outro como este:

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

ou

void sendNetworkPacket(const PlcMsgFactoryImplCallback::NetworkPacket &pdu);
NutCracker
fonte
Para ser sincero, não me sinto satisfeito ... Ambos são sinônimos para o mesmo tipo. Eu posso facilmente ter class C { void f(uint32_t); }; void C::f(unsigned int) { }(desde que o alias corresponda). Então, por que uma diferença aqui? Eles ainda são do mesmo tipo, confirmado por sua citação (que não considero suficiente para explicar) ...
Aconcagua
@Aconcagua: Usar o tipo base ou o alias nunca faz diferença. Um alias nunca é um novo tipo. Sua observação não tem nada a ver com a ambiguidade que você gera, fornecendo o mesmo alias em duas classes base.
Klaus
11
@Aconcagua Acho que o exemplo que você mencionou não é o equivalente certo para a situação da pergunta
NutCracker
Bem, vamos mudar um pouco: vamos nomear as classes A, B e C e o typedef D, e você ainda pode: class C : public A, public B { void f(A::D); }; void C::f(B::D) { }- pelo menos o GCC aceita.
Aconcagua
O autor da pergunta perguntou literalmente 'Por que o compilador trata cada cláusula using como um tipo distinto, embora ambos apontem para o mesmo tipo subjacente?' - e não vejo como a citação esclareceria o porquê , apenas confirma a confusão do controle de qualidade ... Não quero dizer que a resposta está errada , mas não esclarece suficientemente aos meus olhos .. .
Aconcagua
2

Existem dois erros:

  1. Acessando Aliases de Tipo Privado
  2. Referência ambígua aos aliases de tipo

privado-privado

Não vejo um problema que o compilador reclame primeiro sobre o segundo problema, porque o pedido realmente não importa - é necessário corrigir os dois problemas para prosseguir.

público-público

Se você alterar a visibilidade de ambos MultiCmdQueueCallback::NetworkPackete PlcMsgFactoryImplCallback::NetworkPacketpara público ou protegido, o segundo problema (ambiguidade) é óbvio - esses são dois aliases de tipo diferentes, embora tenham o mesmo tipo de dados subjacente. Alguns podem pensar que um compilador "inteligente" pode resolver isso (um caso específico) para você, mas lembre-se de que o compilador precisa "pensar em geral" e tomar decisões com base em regras globais em vez de fazer exceções específicas a cada caso. Imagine o seguinte caso:

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


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

O compilador deve tratar NetworkPacketIDos mesmos? Claro que não. Porque em um sistema de 32 bits, ele size_té de 32 bits e uint64_tsempre de 64 bits. Mas, se quisermos que o compilador verifique os tipos de dados subjacentes, não será possível diferenciá-los em um sistema de 64 bits.

público Privado

Acredito que este exemplo não faz sentido no caso de uso do OP, mas como aqui estamos resolvendo problemas em geral, vamos considerar o seguinte:

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

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

Penso que, neste caso, o compilador deve tratar PlcNetwork::NetworkPacketcomo PlcMsgFactoryImplCallback::NetworkPacketse não tivesse outras opções. Por que ainda se recusa a fazê-lo e culpa pela ambiguidade é um mistério para mim.

PooSH
fonte
"Por que ainda se recusa a fazê-lo e culpar a ambiguidade é um mistério para mim." No C ++, a pesquisa de nome (visibilidade) precede a verificação de acesso. IIRC, li em algum lugar que a lógica é que mudar um nome de privado para público não deve quebrar o código existente, mas não tenho certeza.
LF