Derived1 :: Base e Derived2 :: Base se referem ao mesmo tipo?

12

MSVC, Clang e GCC discordam deste código:

struct Base { int x; };
struct Der1 : public  Base {};
struct Der2 : public  Base {};

struct AllDer : public Der1, public Der2 {
    void foo() {
        Der1::Base::x = 5;
    }
};

Godbolt

GCC:

<source>: In member function 'void AllDer::foo()':    
<source>:10:21: error: 'Base' is an ambiguous base of 'AllDer'    
   10 |         Der1::Base::x = 5;    
      |                     ^    
Compiler returned: 1

Clang dá um erro semelhante e MSVC não dá erro.

Quem está bem aqui?

Suponho que isso seja abordado em [class.member.lookup] , mas tenho dificuldades em entender o que está tentando me dizer para esse caso. Por favor, cite as partes relevantes e, se possível, explique em inglês simples.

PS: Inspirado nesta pergunta Por que a referência à classe base é ambígua com :: -operator através da classe derivada?

PPS: Na verdade, minha dúvida é se Der1::Baserefere ao tipo, que seria Base(e então Der2::Baseé exatamente o mesmo tipo) ou ao subobjeto. Estou convencido de que é o primeiro, mas se for o último, a MSVC estaria certa.

idclev 463035818
fonte
@LanguageLawyer dá mais dicas de que gcc e clang estão certos, mas não estou totalmente convencido de que o mscv esteja errado
idclev 463035818 28/04
11
Obviamente, ambos se referem ao mesmo tipo ::Base, mas a verdadeira questão parece ser um pouco diferente aqui. Existem dois subobjetos do tipo Basee ambos têm um Base::x membro.
MSalters 28/04
@MSalters o PPS apenas expressa minha confusão. Se você tem uma sugestão para um título melhor, sinta-se à vontade para editar, não gosto de mim mesmo (porque sei que a resposta é sim), mas não encontrou algo mais apropriado
idclev 463035818 28/04
11
@ d4rk4ng31 não há herança virtual no código (de propósito)
idclev 463035818 28/04

Respostas:

6

Para responder à pergunta no título, sim, faz Derived1::Basereferência ao nome da classe injetada [class.pre] Base e o mesmo acontece Derived2::Base. Ambos se referem à classe ::Base.

Agora, se Basetivesse um membro estáticox , a pesquisa de Base::xseria inequívoca. Existe apenas um.

O problema neste exemplo é que xé um membro não estático e AllDerpossui dois desses membros. Você pode desambiguar esse acesso xespecificando uma classe base inequívoca da AllDerqual possui apenas um xmembro. Derived1é uma classe base inequívoca e tem um xmembro, portanto, Derived1::xsem ambiguidade, especifica qual dos dois xmembros AllDervocê quer dizer. Basetambém tem apenas um xmembro, mas não é uma base inequívoca de AllDer. Cada instância AllDertem dois sub-objetos do tipo Base. Portanto, Base::xé ambíguo no seu exemplo. E como esse Derived1::Baseé apenas outro nome Base, isso permanece ambíguo.

[class.member.lookup] especifica que xé procurado no contexto do especificador de nome aninhado, para que seja resolvido primeiro. Na verdade, estamos procurando Base::x, não Derived1::x, porque começamos resolvendo Derived1::Basecomo Base. Esta parte é bem-sucedida, há apenas uma xna Base.Nota 12 em [class.member.lookup] explicitamente que o uso de uma pesquisa de nome inequívoca ainda pode falhar quando há vários subobjetos com o mesmo nome. D::inesse exemplo é basicamente o seu Base::x.

MSalters
fonte
então apenas "pode ​​falhar", mas não "deve falhar" e todos os três compiladores estão certos? Considere, por exemplo, a template <typname A,typename B> struct foo : A,Bcom algum código artificial para acessar membros de uma base de Ae B. Nesse caso, eu quero obter um erro
idclev 463035818 28/04
11
@ idclev463035818: Suspeito que seja necessário um diagnóstico "deve falhar" . Em particular, isso falha no acesso aos membros da classe 7.6.1.4/7, "mal formado".
MSalters 28/04
Eu tenho que ler um pouco mais. E eu tenho que fazer alguma pesquisa sobre o msvc, suspeito que alguns sinalizadores são necessários para obter uma saída totalmente compatível, mas tenho que descobrir quais sinalizadores
idclev 463035818 28/04
2

O motivo pelo qual você pode se referir ao nome da classe como um membro da classe é porque o cpp o alia para uso conveniente, como se você tivesse escrito using Base = ::Base;dentro do Base.
O problema que você está enfrentando é que Der1::Baseé Base.
Assim, quando você escreve Der1::Base::x, é o mesmo que Base::x.

Dani
fonte
Eu sei o que é "o problema", mas você não está respondendo à minha pergunta: "Quem está certo? (E por quê?)"
idclev 463035818 28/04
@ idclev463035818: Eu acredito que este é o lado certo. A parte sobre usingestá escrita no padrão, e acho que é a chave para interpretar o que a expressão diz.
Dani
@ idclev463035818: Veja o seguinte exemplo. Eu acho que vai esclarecer um pouco. ideone.com/IqpXjT
Dani
desculpe, mas acho que você não entende o ponto da minha pergunta. Eu quero saber o que é certo de acordo com o padrão, porque vejo diferentes compiladores fazendo coisas diferentes.
Analisar o
Apenas para sua informação, o MSVC (versão 19.25.28614 para x64) falha ao compilar seu exemplo em ideone.com/IqpXjT . Usando cl /c /Wall /WX /Od /MDd /Za /permissive- /std:c++17 main.cppcomo linha de comando, eu recebomain.cpp(7): error C2597: illegal reference to non-static member 'A::x'
heap underrun