¿Derived1 :: Base y Derived2 :: Base se refieren al mismo tipo?

12

MSVC, Clang y GCC no están de acuerdo con este 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

CCG:

<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 da un error similar y MSVC no da ningún error.

¿Quién está aquí?

Supongo que esto está cubierto en [class.member.lookup] , pero tengo dificultades para entender lo que está tratando de decirme para este caso. Por favor, cite las partes relevantes y, si es posible, explique en inglés simple

PD: Inspirado por esta pregunta ¿Por qué la referencia a la clase base es ambigua con :: -operator a través de la clase derivada?

PPS: En realidad, mi duda es si se Der1::Baserefiere al tipo, que sería Base(y luego Der2::Basees exactamente el mismo tipo), o al subobjeto. Estoy convencido de que es el primero, pero si es el último, entonces MSVC estaría en lo cierto.

idclev 463035818
fuente
@LanguageLawyer da más pistas de que gcc y clang son correctos, pero no estoy completamente convencido de que mscv esté mal
idclev 463035818
1
Obviamente, ambos se refieren al mismo tipo ::Base, pero la pregunta real parece ser ligeramente diferente aquí. Hay dos subobjetos de tipo Base, y ambos tienen un Base::x miembro.
MSalters
@MSalters the PPS simplemente expresa mi confusión. Si tiene una sugerencia para un mejor título, no dude en editar, no me gusta (porque sé que la respuesta es sí), pero no encontré algo más apropiado
idclev 463035818
1
@ d4rk4ng31 no hay herencia virtual en el código (a propósito)
idclev 463035818

Respuestas:

6

Para responder la pregunta en el título, sí, hace Derived1::Basereferencia al nombre de la clase inyectada [class.pre] Base y también lo hace Derived2::Base. Ambos se refieren a la clase.::Base .

Ahora, si Basetuviera un miembro estáticox , entonces la búsqueda deBase::x sería inequívoca. Sólo hay uno.

El problema en este ejemplo es que xes un miembro no estático y AllDertiene dos de esos miembros. Puede desambiguar dicho acceso xespecificando una clase base inequívoca de la AllDercual solo tiene un xmiembro. Derived1es una clase base inequívoca y tiene un xmiembro, por lo Derived1::xque especifica inequívocamente a cuál de los dos xmiembros AllDerse refiere. Basetambién tiene un solo xmiembro, pero no es una base inequívoca de AllDer. Cada instancia de AllDertiene dos subobjetos de tipoBase . Por Base::xlo tanto, es ambiguo en su ejemplo. Y dado que Derived1::Basees solo otro nombre para Base, esto sigue siendo ambiguo.

[class.member.lookup] especifica que xse busca en el contexto del especificador de nombre anidado, por lo que debe resolverse primero. De hecho, estamos buscando Base::x, no Derived1::x, porque comenzamos resolviendo Derived1::Basecomo Base. Esta parte tiene éxito, solo hay una xen la Base.Nota 12 en [class.member.lookup] que le dice explícitamente que el uso de una búsqueda de nombres inequívoca aún puede fallar cuando hay múltiples subobjetos con ese mismo nombre. D::ien ese ejemplo es básicamente tu Base::x.

MSalters
fuente
así que solo "puede fallar" pero no "debe fallar" y los tres compiladores tienen razón? Considere, por ejemplo, un template <typname A,typename B> struct foo : A,Bcódigo inventado para acceder a miembros de una base de Ay B. En tal caso, quiero obtener un error
idclev 463035818
1
@ idclev463035818: Sospecho que se requiere un diagnóstico "debe fallar" . En particular, esto falla en el acceso de los miembros de la clase 7.6.1.4/7, "mal formado".
MSalters
Tengo que leer un poco más. Y tengo que investigar un poco sobre msvc, sospecho que se necesitan algunas banderas para obtener una salida totalmente compatible, pero tengo que averiguar qué banderas
idclev 463035818
2

La razón por la que puede referirse al nombre de la clase como miembro de la clase es porque cpp lo alias para un uso conveniente, como si escribiera using Base = ::Base;dentro de Base.
El problema que enfrenta es que Der1::Basees Base.
Por lo tanto, cuando escribes Der1::Base::x, es lo mismo que Base::x.

Dani
fuente
Sé cuál es "el problema", pero no responde a mi pregunta: "¿Quién tiene razón? (¿Y por qué?)"
idclev 463035818
@ idclev463035818: Creo que este es el lado correcto. La parte sobre usingestá escrita en el estándar, y creo que esa es la clave para interpretar lo que dice la expresión.
Dani
@ idclev463035818: Mira el siguiente ejemplo. Creo que lo aclarará un poco. ideone.com/IqpXjT
Dani
lo siento pero creo que no entiendes el punto de mi pregunta. Quiero saber qué es lo correcto de acuerdo con el estándar, porque veo que un compilador diferente hace cosas diferentes. Mirando lo que un compilador en particular hace a un ejemplo en particular, no ayuda a responder la pregunta
idclev 463035818
Solo para su información, MSVC (ver 19.25.28614 para x64) no puede compilar su ejemplo en ideone.com/IqpXjT . Utilizando cl /c /Wall /WX /Od /MDd /Za /permissive- /std:c++17 main.cppcomo línea de comando, obtengomain.cpp(7): error C2597: illegal reference to non-static member 'A::x'
heap underrun