ventajas sirve que portable para pagina oficial historia ejemplos dev desventajas caracteristicas c++ templates gcc clang language-lawyer

c++ - sirve - ¿Qué comprueba un compilador para el código de plantilla desinstalado?



historia de dev c++ (5)

Al principio, si instanciaste a Devired e intentas llamar a badbar , habría un error de compilación:

// ... int main() { Derived<int> d; d.badbar(); return 0; }

produce

error: too few arguments to function call, single argument ''i'' was not specified void badbar() { Base::badfoo(); } // compiles ok ~~~~~~~~~~~~ ^

El compilador no compila el código, que no está instanciado.

Por ejemplo, la siguiente pieza de código compila con gcc-4.9 y clang-602

class Base { public: static void foo() {} void badfoo(int i) {} }; template <typename T> class Derived : public Base { public: void bar() { Base::foo(); } void badbar() { Base::badfoo(); } // compiles ok //static void badbar() { Base::badfoo(); } // compile error //void worsebar() { Base::nonexist(); } // compile error }; int main() { return 0; }

Pero las líneas comentadas no se compilarán.

Mis preguntas son:

  1. ¿Por qué badbar() compila pero worsebar() no?

  2. Si cambio badbar() a estático, tampoco compilará, independientemente de si base::badfoo es estático o no.

  3. ¿El estándar dice algo sobre lo que se debe controlar en esta situación? gcc4.4 en realidad se niega a compilar incluso badbar() .

Actualizar:

El problema 1 ha sido explicado por varias respuestas, pero parece que los compiladores también hacen un esfuerzo adicional para verificar la sobrecarga, le sucede a gcc 4.4.3 y 4.8.2, pero no a 4.7.2 y 4.9.1.

Problema 2: Como señaló Marco A., clang no compilará pero gcc4.9 aún pasará. Sin embargo, gcc4.2 y gcc4.4 rechazan el código, y el error del que se quejan es "no función de coincidencia" en lugar de "llamar al miembro no estático sin un objeto" en clang. Parece que no hay una respuesta concluyente a esta pregunta, así que estoy agregando una etiqueta de abogado de idioma como sugirió Daniel Frey.

Más actualización:

Creo que para la pregunta 2 la respuesta es bastante clara ahora: depende del compilador si agregar una declaración estática cambiará el diagnóstico. Varía de compilador a compilador y diferentes versiones del mismo compilador. El abogado de idiomas no apareció, voy a aceptar la respuesta de Daniel Frey como mejor explicó la primera pregunta. Pero también vale la pena leer las respuestas de Marco A. y Hadi Brais.


Antes de instanciar cualquier tipo o emitir cualquier código, el compilador construye incrementalmente una tabla de todos los símbolos que se han declarado. Si se ha utilizado un símbolo no declarado, emite un error. Es por eso que la barra peor no se compilará. Por otro lado, badfoo ha sido declarado y así compila badbar. En este punto inicial del proceso de compilación, el compilador no comprobará si la llamada a badfoo en realidad coincide con el badfoo declarado.

Como el tipo Derivado no se ha instanciado en ningún lugar del código, el compilador no emitirá ningún código al respecto. En particular, badbar será descuidado.

Ahora cuando declara una instancia de Derivado (como Derived <int>) pero sin utilizar ninguno de sus miembros, el compilador simplemente creará un tipo con aquellos miembros que se han usado y omitirá los otros. Aún así, no hay error con respecto a badbar.

Sin embargo, al declarar una instancia de Derivado y llamar a la barra oculta, se requeriría una instancia del método de barra oblicua, por lo que el compilador creará un tipo con la barra oculta y lo compilará. Esta vez, el compilador advierte que badfoo no está realmente declarado y, por lo tanto, emite un error.

Este comportamiento está documentado en el estándar de C ++ en la sección 14.7.1.

A menos que un miembro de una plantilla de clase o miembro haya sido explícitamente instanciado o explícitamente especializado, la especialización del miembro se instanciará implícitamente cuando se haga referencia a la especialización en un contexto que requiera que exista la definición de miembro.

Finalmente, si badbar era estático y el compilador lo instanciaba (porque se ha utilizado), entonces el compilador emitirá un error que badfoo no existe. Ahora, si pasa un argumento entero a badfoo, se emitirá otro error que indica que un método estático no puede acceder a un miembro de instancia porque no hay ninguna instancia en primer lugar.

Editar

El compilador no está obligado a NO informar errores semánticos en tipos de plantilla desinstalados. El estándar simplemente dice que no tiene que ser así, pero puede hacerlo. En cuanto a dónde dibujar la línea está abierto para el debate. Vea this discusión sobre un problema relacionado en clang:

¿Qué plantillas desinstaladas analizamos? Por motivos de rendimiento, no creo que debamos analizar todas las plantillas desinstaladas, ya que podemos encontrarnos analizando repetidamente una gran parte de Boost y STL, etc.

Así que el análisis de plantillas desinstalado cambia con diferentes versiones de clang y gcc de diferentes maneras. Pero, de nuevo, según el estándar: no hay ningún requisito para informar errores en plantillas desinstaladas, por supuesto.


Considera [temp.res] / 8:

Si no se puede generar una especialización válida para una plantilla, y esa plantilla no se crea una instancia, la plantilla está mal formada, no se requiere diagnóstico.

Esto (en particular, el bit "no requiere diagnóstico") hace que el comportamiento de cualquier compilador sea compatible con respecto a la worsebar . Las discrepancias de las implementaciones en este tipo de código son solo problemas de QoI: los compiladores comunes hacen algunos análisis y se quejan. Es difícil decir cuándo exactamente, y debería estar preparado para volver al código de plantilla al actualizar o alternar su implementación.


El estándar solo requiere que la búsqueda de nombre suceda en la fase 1, cuando la plantilla se analiza por primera vez. Esto aparece badfoo en badbar y es por eso que el código se compila. El compilador no está obligado a hacer la resolución de sobrecarga en ese momento.

La resolución de sobrecarga (que siempre ocurre como un paso separado después de la búsqueda del nombre) se realiza en la fase 2 cuando se badbar una instancia de badbar , que no es el caso en su ejemplo. Este principio se puede encontrar en

3.4 Búsqueda de nombres [basic.lookup]

1 Las reglas de búsqueda de nombre se aplican de manera uniforme a todos los nombres (incluidos typedef-names (7.1.3), namespace-names (7.3) y class-names (9.1)) siempre que la gramática permita dichos nombres en el contexto discutido por una regla particular . La búsqueda de nombre asocia el uso de un nombre con una declaración (3.1) de ese nombre. La búsqueda de nombres encontrará una declaración inequívoca para el nombre (ver 10.2). La búsqueda de nombres puede asociar más de una declaración con un nombre si encuentra que el nombre es un nombre de función; se dice que las declaraciones forman un conjunto de funciones sobrecargadas (13.1). La resolución de sobrecarga (13.3) tiene lugar después de que la búsqueda de nombres ha tenido éxito. Las reglas de acceso (Cláusula 11) se consideran solo una vez que la búsqueda de nombre y la resolución de sobrecarga de la función (si corresponde) han tenido éxito. Solo después de la búsqueda del nombre, la resolución de sobrecarga de la función (si corresponde) y la verificación de acceso han tenido éxito son los atributos introducidos por la declaración del nombre utilizados más adelante en el procesamiento de la expresión (Cláusula 5).

(Énfasis mío)

Por lo tanto, diría que el (los) compilador (es) son correctos para aceptar el código, aunque no estoy seguro de que estén obligados a hacerlo.

Para ver el código rechazado , necesita crear badbar instancia de la badbar .


Para aclarar algo ... esta versión del código se compila muy bien en clang y gcc

class Base { public: static void foo() {} void badfoo(int a) {} }; template <typename T> class Derived : public Base { public: void bar() { Base::foo(); } void badbar() { Base::badfoo(); } };

ya que

[temp.res] / p8

Si no se puede generar una especialización válida para una plantilla, y esa plantilla no se crea una instancia, la plantilla está mal formada, no se requiere diagnóstico.

Tanto gcc como clang no son necesarios para diagnosticar esto . Este también cae en el mismo caso que el anterior (clang emite un error, gcc no)

class Base { public: static void foo() {} void badfoo(int a) {} }; template <typename T> class Derived : public Base { public: void bar() { Base::foo(); } static void badbar() { Base::badfoo(); } };

El caso con

void worsebar() { Base::nonexist(); }

es diferente ya que viola la búsqueda de nombres [temp.res] / p9

Al buscar la declaración de un nombre utilizado en una definición de plantilla, las reglas de búsqueda habituales (3.4.1, 3.4.2) se utilizan para nombres no dependientes

y [temp.res] / p10

Si un nombre no depende de un parámetro de plantilla (como se define en 14.6.2), una declaración (o un conjunto de declaraciones) para ese nombre estará dentro del alcance en el punto donde el nombre aparece en la definición de la plantilla

Descargo de responsabilidad: todo lo anterior no se aplica a MSVC, que felizmente pospone todo esto a la segunda fase de la búsqueda.

Editar:

en el caso

class Base { public: static void foo() {} void badfoo(int i) {} }; template <typename T> class Derived : public Base { public: static void badbar() { Base::badfoo(); } // static function

clang desencadena un error mientras que gcc no lo hace . Esto cae en el primer caso, ya que la búsqueda de nombres es exitosa, pero clang realiza una comprobación adicional: dado que badfoo es una función miembro, intenta construir una expresión de referencia de miembro implícita válida. Luego capta el hecho de que una función miembro se llama implícitamente desde una función estática y detecta la falta de coincidencia del contexto. Este diagnóstico depende completamente del compilador en este punto (no lo sería en el caso de una instanciación).