c++ gcc clang language-lawyer name-lookup

c++ - Expresión de acceso de miembro ambigua: ¿Clang rechaza un código válido?



gcc language-lawyer (6)

No es una respuesta,

sólo mi pequeña contribución:

Eliminando plantillas, pero manteniendo los mismos nombres:

struct A { struct TemplateClass { void method() {} }; }; struct B { struct TemplateClass { void method() {} static void static_method(A::TemplateClass u) { u.TemplateClass::method(); } }; }; int main() { A::TemplateClass c; B::TemplateClass::static_method(c); }

da

Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for ONLINE_EVALUATION_BETA2 Copyright 1988-2008 Comeau Computing. All rights reserved. MODE:strict errors C++ C++0x_extensions "ComeauTest.c", line 11: error: ambiguous class member reference -- type "B::TemplateClass::TemplateClass" (declared at line 7) used in preference to type "A::TemplateClass::TemplateClass" (declared at line 2) u.TemplateClass::method(); ^ "ComeauTest.c", line 11: error: qualified name is not a member of class "A::TemplateClass" or its base classes u.TemplateClass::method(); ^ 2 errors detected in the compilation of "ComeauTest.c".

Desde n3242

Nombres declarados localmente [temp.local]

Al igual que las clases normales (sin plantilla), las plantillas de clase tienen un nombre de clase inyectado (Cláusula 9). El nombre de clase inyectado se puede usar con o sin una lista de argumentos de plantilla. Cuando se usa sin una lista de argumentos de plantilla, es equivalente al nombre de clase inyectado seguido de los parámetros de plantilla de la plantilla de clase incluida en <>.

(...)

Dentro del alcance de una especialización de plantilla de clase o especialización parcial, cuando el nombre de clase inyectada no es seguido por un <, es equivalente al nombre de clase inyectada seguido de los argumentos de plantilla de la especialización de plantilla de clase o la especialización parcial encerrado en <>.

(...)

Una búsqueda que encuentra un nombre de clase inyectada (10.2) puede resultar en una ambigüedad en ciertos casos

Tengo un código que, a los efectos de esta pregunta, se reduce a

template<typename T> class TemplateClass : public T { public: void method() {} template<typename U> static void static_method(U u) { u.TemplateClass::method(); } }; class EmptyClass {}; int main() { TemplateClass<TemplateClass<EmptyClass> > c; TemplateClass<EmptyClass>::static_method(c); }

He intentado compilarlo con varias versiones de dos compiladores. CGC 4.2, 4.4, 4.6 aceptarlo sin queja. Clang 2.9 y el troncal SVN a partir del 14 de noviembre lo rechazan con el siguiente mensaje de error:

example.cc:6:38: error: lookup of ''TemplateClass'' in member access expression is ambiguous static void static_method(U u) { u.TemplateClass::method(); } ^ example.cc:13:3: note: in instantiation of function template specialization ''TemplateClass<EmptyClass>::static_method<TemplateClass<TemplateClass<EmptyClass> > >'' requested here TemplateClass<EmptyClass>::static_method(c); ^ example.cc:2:7: note: lookup in the object type ''TemplateClass<TemplateClass<EmptyClass> >'' refers here class TemplateClass : public T { ^ example.cc:2:7: note: lookup from the current scope refers here 1 error generated.

¿Cuál está mal? Puedo trabajar alrededor de Clang cambiando

static void static_method(U u) { u.TemplateClass::method(); }

a

static void static_method(U u) { u.TemplateClass<T>::method(); }

pero me gustaría tener confianza en mi comprensión de cuándo está bien eliminar los parámetros de la plantilla.

EDITAR: Pensé que la ambigüedad estaba entre las dos instancias de TemplateClass . El siguiente código se compila con GCC y Clang, lo que pone en duda esa hipótesis:

class E {}; template<typename T> class A : public T { public: void method() {} }; int main() { A<A<E> > a; a.A::method(); }


Como nunca había usado Clang, estaba muy interesado en este problema. (Irónico, sí lo sé).

La compatibilidad con Clang C ++ indica que hay varias cosas con respecto a las plantillas que otros compiladores (en particular GCC) procesan de las que se quejarán. Estas son cosas que están débilmente definidas en el estándar ("bueno, no deberías permitir esto ... pero puedes"); Casi todas involucran plantillas. Ninguno de estos se parece exactamente a tu problema, pero son lo suficientemente cercanos como para ser informativos, y ciertamente vale la pena leerlos.

Entonces, no parece que Clang esté roto, es solo que Clang es más delicado que los demás.


Creo que Clang está rechazando correctamente este código.

La ambigüedad que encuentra Clang se puede reproducir con un ejemplo menos complicado:

template<typename T> class TemplateClass { public: void method() {} template<typename U> static void static_method(U u) { u.TemplateClass::method(); } }; struct A {}; struct B {}; int main() { TemplateClass<A> c; TemplateClass<B>::static_method(c); }

Aquí se omite la herencia en la plantilla y se utilizan dos clases independientes para las instancias. El error producido por clang sigue siendo el mismo.

En primer lugar, en el ámbito de TemplateClass<T> el nombre TemplateClass refiere a TemplateClass<T> , debido a la inyección del nombre de la clase. Esta es la razón por la que el método estático puede usar TemplateClass::method lugar de un TemplateClass::method TemplateClass<T>::method más explícito.

La búsqueda de nombres utilizada para interpretar el u.TemplateClass::method en el método estático se define en "3.4.5 Acceso de miembros de clase [base.lookup.classref]" de los estándares C ++ 11 y C ++ 98.

La parte relevante es 3.4.5 / 4:

Si el id-expresión en un acceso de miembro de clase es un id-calificado del formulario

class-name-or-namespace-name::...

[...]

Este es el caso aquí. La expresión-id es la parte a la derecha de . y en nuestro caso, este es el nombre calificado TemplateClass::method .

[...]
el nombre de la clase nombre o espacio de nombres que sigue a . o -> operador se busca tanto en el contexto de la expresión postfix completa como en el alcance de la clase de la expresión del objeto.

El "alcance de toda la expresión-postfix " es el cuerpo de la función estática, y en esta función estática TemplateClass refiere a TemplateClass<B> , ya que la función es un miembro de esa clase (llamamos TemplateClass<B>::static_method ).

Entonces, en este ámbito, el nombre hace referencia a TemplateClass<B> .

La "expresión de objeto" es la parte izquierda de . , en nuestro caso c . La clase de c es TemplateClass<A> y en el ámbito de esta clase, TemplateClass refiere a TemplateClass<A> .

Entonces, dependiendo del alcance utilizado para la búsqueda, el nombre se refiere a una entidad diferente.

El estándar ahora dice:

Si el nombre se encuentra en ambos contextos, el nombre de la clase nombre o espacio de nombres se referirá a la misma entidad.

Este no es el caso en nuestro programa. El programa está mal formado y el compilador debe enviar un mensaje de diagnóstico.

La ambigüedad se mantiene igual si reemplaza B con TemplateClass<A> , como se usa en la pregunta.


Creo que la ambigüedad es porque TemplateClass está dos veces en la herencia TemplateClass : (TemplateClass : EmptyClass)

Hace u.TemplateClass::method(); mean u.TemplateClass<TemplateClass<EmptyClass> >::method(); o u.TemplateClass<EmptyClass> >::method(); ?

Quizás GCC tiene el derecho estándar, pero en cualquier caso debería agregar el <T> .


Cuando llamamos a estas dos líneas:

TemplateClass<TemplateClass<EmptyClass> > c; TemplateClass<std::string>::static_method(c);

entonces el argumento de tipo U es el tipo del objeto c:

TemplateClass<TemplateClass<EmptyClass> >

Dejemos static_method , y hagamos un experimento:

#include <iostream> #include <typeinfo.h> using namespace std; template<typename T> class TemplateClass : public T { public: void method(int i) { cout << i << ": "; cout << typeid(*this).name() << endl; } }; class EmptyClass { }; void main() { TemplateClass<TemplateClass<EmptyClass> > u; u.method(1); u.TemplateClass::method(2); u.TemplateClass<EmptyClass>::method(3); u.TemplateClass<TemplateClass<EmptyClass> >::method(4); }

La salida es:

1: class TemplateClass<class TemplateClass<class EmptyClass> > 2: class TemplateClass<class TemplateClass<class EmptyClass> > 3: class TemplateClass<class EmptyClass> 4: class TemplateClass<class TemplateClass<class EmptyClass> >

En los cuatro casos (y dentro de static_method ) llamamos TemplateClass<T>::method , y el nombre de tipo dado entre u. y :: dará el tipo real T:

  • El caso # 1 es el predeterminado, aquí T viene dado por la declaración de u.
  • El caso # 4 también es trivial.
  • El caso # 2 parece como si el compilador hubiera adivinado el tipo de argumento de TemplateClass, que es trivialmente el que se da en la declaración de u.
  • El caso # 3 es muy interesante. Supongo que el tipo de función de conversión se realizó aquí, desde TemplateClass<TemplateClass<EmptyClass> >::method hasta TemplateClass<EmptyClass>::method .

No sé si este comportamiento es parte del estándar C ++.

EDITAR:

En realidad el caso # 3 no es el casting, estos son nombres calificados . Entonces, en conclusión, Clang no tiene conocimiento de esta sintaxis de calificación, mientras que tanto GCC como Visual C ++ 2010 sí lo son.


En ISO / IEC 14882: 2011 (E), "14.6.1 Nombres declarados localmente [temp.local]", [# 5] dice:

Cuando se usa el nombre normal de la plantilla (es decir, el nombre del ámbito que la contiene, no el nombre de clase inyectada), siempre se refiere a la plantilla de clase en sí y no a una especialización de la plantilla. [Ejemplo:

template<class T> class X { X* p; // meaning X<T> X<T>* p2; X<int>* p3; ::X* p4; // error: missing template argument list // ::X does not refer to the injected-class-name }; — end example ]

Esto me lleva a creer que en tu ejemplo u.TemplateClass::method(); es equivalente a u.TemplateClass<T>::method(); y si Clang da un error en un caso y compila limpiamente en el otro caso, entonces es un error de Clang.