¿Qué es exactamente "roto" con la instanciación de plantilla de dos fases de Microsoft Visual C++?
templates visual-c++ (5)
Al leer preguntas, comentarios y respuestas sobre SO, todo el tiempo escucho que MSVC no implementa correctamente la búsqueda / instanciación de plantilla en dos fases.
Por lo que entiendo hasta ahora, MSVC ++ solo está haciendo una verificación de sintaxis básica en las clases y funciones de la plantilla y no comprueba que los nombres utilizados en la plantilla hayan sido declarados al menos o algo así.
¿Es esto correcto? ¿Qué me estoy perdiendo?
respuesta corta
Deshabilitar las extensiones de idioma con / Za
respuesta más larga
Estuve investigando este tema últimamente y me asombró que en VS 2013, el siguiente ejemplo del estándar [temp.dep] p3 produzca resultados incorrectos:
typedef double A;
template<class T> class B {
public:
typedef int A;
};
template<class T> struct X : B<T> {
public:
A a;
};
int main()
{
X<int> x;
std::cout << "type of a: " << typeid(x.a).name() << std::endl;
}
se imprimirá:
type of a: int
mientras que debería imprimir double
. La solución para hacer que VS estándar sea compatible es deshabilitar las extensiones de idioma (opción / Za), ahora el tipo de xa se resolverá como el doble, también otros casos de uso de nombres dependientes de clases base serán estándar. No estoy seguro de si esto permite la búsqueda en dos fases.
Ahora que MSVC tiene implementada la mayoría de las búsquedas de nombre de dos fases, espero que esta publicación en el blog responda esta pregunta por completo: la búsqueda de nombres en dos fases llega a MSVC (blog de VC ++)
Copiaré un ejemplo de mi "notebook"
int foo(void*);
template<typename T> struct S {
S() { int i = foo(0); }
// A standard-compliant compiler is supposed to
// resolve the ''foo(0)'' call here (i.e. early) and
// bind it to ''foo(void*)''
};
void foo(int);
int main() {
S<int> s;
// VS2005 will resolve the ''foo(0)'' call here (i.e.
// late, during instantiation of ''S::S()'') and
// bind it to ''foo(int)'', reporting an error in the
// initialization of ''i''
}
Se supone que el código anterior compila en un compilador estándar de C ++. Sin embargo, MSVC (2005 y 2010 Express) informará un error debido a la implementación incorrecta de la búsqueda en dos fases.
Y si miras más de cerca, el problema es en realidad de dos capas. En la superficie, es el hecho obvio que el compilador de Microsoft no realiza la búsqueda temprana (primera fase) de una expresión no dependiente foo(0)
. Pero lo que hace después de eso realmente no se comporta como una implementación adecuada de la segunda fase de búsqueda.
La especificación del lenguaje indica claramente que durante la segunda fase de búsqueda solo los espacios de nombres nominados por ADL se extienden con declaraciones adicionales acumuladas entre el punto de definición y el punto de creación de instancias. Mientras tanto, la búsqueda que no es ADL (es decir, la búsqueda ordinaria de nombres no calificada) no se extiende en la segunda fase; aún se ven esas y solo aquellas declaraciones que fueron visibles en la primera fase.
Eso significa que en el ejemplo anterior tampoco se supone que el compilador vea void foo(int)
en la segunda fase. En otras palabras, el comportamiento del MSVC no puede describirse simplemente por "MSVC pospone todas las búsquedas hasta la segunda fase". Lo que MSVC implementa tampoco es una implementación adecuada de la segunda fase.
Para ilustrar mejor el problema, considere el siguiente ejemplo
namespace N {
struct S {};
}
void bar(void *) {}
template <typename T> void foo(T *t) {
bar(t);
}
void bar(N::S *s) {}
int main() {
N::S s;
foo(&s);
}
Tenga en cuenta que aunque la llamada de bar(t)
dentro de la definición de la plantilla es una expresión dependiente resuelta en la segunda fase de búsqueda, aún debe resolverse en la void bar(void *)
. En este caso, ADL no ayuda al compilador a encontrar la void bar(N::S *s)
, mientras que la búsqueda regular no calificada no debe "ampliarse" en la segunda fase y, por lo tanto, no debe ver la void bar(N::S *s)
tampoco.
Sin embargo, el compilador de Microsoft resuelve la llamada a la void bar(N::S *s)
. Esto es incorrecto.
El problema sigue presente en su gloria original en VS2015.
El proyecto Clang tiene una muy buena descripción de la búsqueda en dos fases, y cuáles son las diferentes diferencias de implementación: http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html
Versión corta: búsqueda de dos fases es el nombre del comportamiento definido estándar de C ++ para la búsqueda de nombres dentro del código de la plantilla. Básicamente, algunos nombres se definen como dependientes (las reglas son un poco confusas), estos nombres deben buscarse al crear una instancia de la plantilla, y los nombres independientes deben buscarse al analizar la plantilla. Esto es difícil de implementar (aparentemente) y confuso para los desarrolladores, por lo que los compiladores tienden a no implementarlo en el estándar. Para responder a su pregunta, parece que Visual C ++ retrasa todas las búsquedas, pero busca el contexto de la plantilla y el contexto de creación de instancias, por lo que acepta una gran cantidad de código que el estándar dice que no debería. No estoy seguro de si no acepta el código que debería o, peor aún, lo interpreta de manera diferente, pero parece posible.
Históricamente, gcc tampoco implementó la búsqueda de nombres de dos fases correctamente. Al parecer es muy difícil de conseguir, o al menos no había mucho incentivo ...
- gcc 4.7 afirma implementarlo correctamente , por fin
- CLang apunta a implementarlo, descubriendo errores, se hace en ToT y entrará en 3.0
No sé por qué los escritores de VC ++ nunca decidieron implementar esto correctamente, la implementación de un comportamiento similar en CLang (para microsoft compabitility) insinúa que podría haber alguna ganancia de rendimiento para retrasar la creación de instancias de plantillas al final de la unidad de traducción (que no significa implementar la búsqueda de forma incorrecta, sino hacerlo aún más difícil). Además, dada la aparente dificultad de una implementación correcta, puede haber sido más simple (y más barato).
Me gustaría señalar que VC ++ es primero, y ante todo, un producto comercial. Es impulsado por la necesidad de satisfacer a sus clientes.