name keywords etiquetas etiqueta ejemplos description content c++ templates c++11 gcc name-lookup

c++ - keywords - meta tags ejemplos



¿Por qué esta búsqueda de nombre dependiente encuentra un identificador global en lugar del método? (1)

Cuando el compilador intenta resolver i.template hi<T>(); encuentra hi en el espacio de nombre global en lugar del método hi en i ( ideone ). ¿Por qué?

#include <cstdio> // Define ''hi'' and ''bye'' in the global namespace; these should *not* be used template<typename T> struct hi { }; template<typename T> struct bye { }; // Foo needs to be templated for Foo::Inner to be a dependent type (I think) template<typename T> struct Foo { struct Inner { // This is a plain-old templated member function of Inner, yes? template<typename U> void hi() { std::printf("hi!/n"); } // This is a plain-old member function of Inner void bye() { std::printf("bye!/n"); } }; void sayStuff() { Inner i; i.template hi<T>(); // Fails to compile -- finds global hi instead of member i.bye(); // Compiles fine, finds member } }; int main() { Foo<int> f; f.sayStuff(); return 0; }

Estoy usando g ++ 4.9.1 / 4.9.2 ( -std=c++11 ). El mensaje de error exacto:

prog.cpp: In member function ''void Foo<T>::sayStuff()'': prog.cpp:19:5: error: invalid use of ''struct hi<T>'' i.template hi<T>(); ^

Este código funciona bien con Clang y VS2013, pero genera un error en g ++ y EDG. Pero, ¿qué compiladores tienen razón?

¿Hay alguna manera de resolver esto además de cambiar el nombre del miembro? En mi código real, el conflicto surge cuando un tipo del std nombres std (que se ha importado using namespace std , por ejemplo) tiene el mismo nombre que una de mis funciones miembro. Obviamente, me gustaría que mi código de implementación sea robusto y no cause conflictos de nombres aleatorios en el código de usuario.


Según mi leal saber y entender, esto es lo que está pasando.

DR228 dice:

[Votado en WP en la reunión de abril de 2003].

Considere el siguiente ejemplo:

template<class T> struct X { virtual void f(); }; template<class T> struct Y { void g(X<T> *p) { p->template X<T>::f(); } };

Esto es un error porque X no es una plantilla de miembro; 14.2 [temp.names] El párrafo 5 dice:

Si un nombre prefijado por la plantilla de palabra clave no es el nombre de una plantilla de miembro, el programa está mal formado.

De alguna manera, esto tiene perfecto sentido: se encuentra que X es una plantilla que usa la búsqueda ordinaria aunque p tiene un tipo dependiente. Sin embargo, creo que esto hace que el uso del prefijo de plantilla sea aún más difícil de enseñar.

¿Fue intencionalmente prohibido?

Resolución propuesta (4/02):

Elide el primer uso de la palabra "miembro" en 14.2 [temp.names] párrafo 5 de modo que su primera oración se lea:

Si un nombre prefijado por la plantilla de palabra clave no es el nombre de una plantilla, el programa está mal formado.

Sin embargo, en el borrador más actual disponible públicamente del estándar C ++ N4296 aparece la siguiente redacción en §14.2.5:

Un nombre prefijado por la plantilla de palabra clave será una plantilla-id o el nombre se referirá a una plantilla de clase. [ Nota : la template palabra clave no se puede aplicar a los miembros que no sean plantillas de las plantillas de clase. -finalizar ] [ Nota : como en el caso del prefijo typename , el prefijo de la template está permitido en los casos en que no es estrictamente necesario; es decir, cuando el especificador de nombre anidado o la expresión a la izquierda de -> o . no depende de un parámetro de plantilla , o el uso no aparece en el alcance de una plantilla. -finalizar nota ]

[ Ejemplo :

template <class T> struct A { void f(int); template <class U> void f(U); }; template <class T> void f(T t) { A<T> a; a.template f<>(t); // OK: calls template a.template f(t); // error: not a template-id } template <class T> struct B { template <class T2> struct C { }; }; // OK: T::template C names a class template: template <class T, template <class X> class TT = T::template C> struct D { }; D<B<int> > db;

-Final ejemplo ]

Esta fraseología sonaba similar, pero lo suficientemente diferente como para ir a cavar. Descubrí que en el borrador N3126 la redacción se cambió a esta versión.

Pude vincular este cambio a este DR96 :

La siguiente es la redacción de 14.2 [temp.names], párrafos 4 y 5, que analiza el uso de la palabra clave "plantilla" a continuación. o -> y en nombres calificados.

{recorte}

El objetivo de esta característica es decir que la palabra clave "plantilla" es necesaria para indicar que un "<" comienza una lista de parámetros de plantilla en ciertos contextos. Las restricciones en el párrafo 5 dejan abierto para debatir ciertos casos.

En primer lugar, creo que debería quedar más claro que el nombre de la plantilla debe ir seguido de una lista de argumentos de plantilla cuando se utiliza la palabra clave "plantilla" en estos contextos. Si no lo aclaramos, tendríamos que agregar varias aclaraciones semánticas en su lugar. Por ejemplo, si dices "p-> plantilla f ()", y "f" es un conjunto de sobrecarga que contiene tanto plantillas como plantillas: a) ¿es esto válido? b) ¿se ignoran las plantillas en el conjunto de sobrecarga? Si el usuario se ve obligado a escribir "p-> plantilla f <> ()", está claro que esto es válido, y es igualmente claro que las plantillas en el conjunto de sobrecarga se ignoran. Como esta característica se agregó puramente para proporcionar una guía sintáctica, creo que es importante que no tenga implicaciones semánticas.

Esencialmente, el cambio muy sutil de DR228 se perdió durante una revisión posterior; sin embargo, debido a que no se colocó una restricción similar, la intención del DR228 probablemente aún se mantenga a menos que haya habido otra revisión en el estándar. Esto significa que la búsqueda de plantilla tiene que ocurrir globalmente en este caso, aunque es un tipo dependiente.

Veamos nuestras reglas de búsqueda de nombres §3.4.5.1:

En una expresión de acceso de miembro de clase (5.2.5), si el . o -> token es seguido inmediatamente por un identificador seguido de un < , el identificador debe buscarse para determinar si < es el comienzo de una lista de argumentos de plantilla (14.2) o un operador menor que. El identificador se busca por primera vez en la clase de la expresión del objeto. Si no se encuentra el identificador, entonces se busca en el contexto de la expresión de postfijo completa y se debe nombrar una plantilla de clase.

Esto parece indicar explícitamente que en baz.foo->template bar<T>(); Primero buscaremos en el contexto de la clase, esto incluye la búsqueda de plantilla estándar. Una vez hecho esto, y si no se encuentra nada, si la forma de la expresión es correcta saltamos al contexto de toda la expresión. En esencia, se ha promocionado y la búsqueda de ese nombre debe realizarse de la misma manera si la línea acaba de leer template bar<T>(); Realmente, ya sabíamos esto de DR228. Solo quería verificar y confirmar. La verdadera pregunta es qué plantilla debe tener prioridad, la del alcance global o la del alcance de la clase.

Para eso, ahora debemos pedir una búsqueda de nombres no calificada, porque ahora la barra se está considerando en el mismo contexto que foo, por lo que ya no sigue las reglas de búsqueda de miembros, sigue reglas de búsqueda de plantillas normales e incondicionales, que, naturalmente, prefieren la versión local .

En resumen, parece que Clang y MSVC muestran un comportamiento correcto, y GCC y EDG no lo hacen en este caso.

Mi mejor conjetura sobre por qué GCC está equivocado es elegir el contexto incorrecto para asignar a la expresión después de que se desencadena la regla. En lugar de colocar el contexto en el mismo nivel que la expresión postfijo, ¿puede ser simplemente colocarlo en el nivel global por accidente? Tal vez simplemente se salta el primer paso de búsqueda? (Pero esto es meramente especulación, tendría que averiguar dónde buscar en la fuente del CCG para decir). Esto también explicaría por qué la solución de @Mikael Persson de cambiar la búsqueda por una calificada hizo que la compilación comenzara de nuevo.

El informe de errores enlazado del asker hace que las personas hablen de por qué se debe considerar el alcance global, y debería ser, pero parece bastante sencillo que a una coincidencia de alcance local se le deba dar mayor prioridad que la global. También parece que ha habido un poco de actividad allí recientemente.