¿Por qué los mensajes de error de plantilla C ++ son tan horribles?

28

Las plantillas de C ++ son conocidas por generar mensajes de error largos e ilegibles. Tengo una idea general de por qué los mensajes de error de plantilla en C ++ son tan malos. Esencialmente, el problema es que el error no se activa hasta que el compilador encuentra una sintaxis que no es compatible con cierto tipo en una plantilla. Por ejemplo:

template <class T>
void dosomething(T& x) { x += 5; }

Si Tno es compatible con el +=operador, el compilador generará un mensaje de error. Y si esto sucede en lo profundo de una biblioteca en algún lugar, el mensaje de error puede tener miles de líneas de largo.

Pero las plantillas C ++ son esencialmente solo un mecanismo para escribir en tiempo de compilación. Un error de plantilla de C ++ es conceptualmente muy similar a un error de tipo de tiempo de ejecución que puede ocurrir en un lenguaje dinámico como Python. Por ejemplo, considere el siguiente código de Python:

def dosomething(x):
   x.foo()

Aquí, si xno tiene un foo()método, el intérprete de Python lanza una excepción y muestra un seguimiento de la pila junto con un mensaje de error bastante claro que indica el problema. Incluso si el error no se activa hasta que el intérprete se encuentre dentro de alguna función de la biblioteca, el mensaje de error en tiempo de ejecución aún no es tan malo como el vómito ilegible arrojado por un compilador típico de C ++. Entonces, ¿por qué un compilador de C ++ no puede ser más claro sobre lo que salió mal? ¿Por qué algunos mensajes de error de plantilla C ++ literalmente hacen que la ventana de mi consola se desplace durante más de 5 segundos?

Canal72
fuente
66
Algunos compiladores tienen mensajes de error horribles, pero otros son realmente buenos ( clang++guiño guiño).
Benjamin Bannier
2
Entonces, ¿preferiría que sus programas fallaran en tiempo de ejecución, enviados, a manos de un cliente, en lugar de fallar en tiempo de compilación?
P Shved
13
@Pavel, no. Esta pregunta no trata sobre las ventajas / desventajas del tiempo de ejecución frente a la verificación de errores en tiempo de compilación.
Canal72
1
Como ejemplo de grandes errores de plantilla C ++, FWIW: codegolf.stackexchange.com/a/10470/7174
kebs

Respuestas:

28

Los mensajes de error de la plantilla pueden ser notorios, pero de ninguna manera son siempre largos e ilegibles. En este caso, el mensaje de error completo (de gcc) es:

test.cpp: In function void dosomething(T&) [with T = X]’:
test.cpp:11:   instantiated from here
test.cpp:6: error: no match for operator+=’ in x += 5

Como en su ejemplo de Python, obtiene un "seguimiento de pila" de puntos de creación de instancias de plantilla y un mensaje de error claro que indica el problema.

A veces, los mensajes de error relacionados con la plantilla pueden ser mucho más largos, por varias razones:

  • El "rastro de la pila" podría ser mucho más profundo
  • Los nombres de tipo pueden ser mucho más largos, ya que las plantillas se instancian con otras instancias de plantilla como sus argumentos, y se muestran con todos sus calificadores de espacio de nombres
  • Cuando falla la resolución de sobrecarga, el mensaje de error puede contener una lista de sobrecargas candidatas (que pueden contener algunos nombres de tipo muy largos)
  • Se puede informar el mismo error muchas veces, si se crea una instancia de una plantilla no válida en muchos lugares

La principal diferencia con Python es el sistema de tipo estático, lo que lleva a la necesidad de incluir los nombres de tipo (a veces largos) en el mensaje de error. Sin ellos, a veces sería muy difícil diagnosticar por qué falló la resolución de sobrecarga. Con ellos, tu desafío ya no es adivinar dónde está el problema, sino descifrar los jeroglíficos que te dicen dónde está.

Además, la comprobación en tiempo de ejecución significa que el programa se detendrá en el primer error que encuentre, mostrando solo un mensaje. Un compilador puede mostrar todos los errores que encuentra, hasta que se da por vencido; al menos en C ++, no debería detenerse en el primer error en el archivo, ya que puede ser consecuencia de un error posterior.

Mike Seymour
fuente
44
¿Podría dar un ejemplo de un error como consecuencia de un error posterior?
Ruslan
12

Algunas de las razones obvias incluyen:

  1. Historia. Cuando gcc, MSVC, etc., eran nuevos, no podían permitirse el lujo de utilizar mucho espacio extra para almacenar datos para producir mejores mensajes de error. La memoria era tan escasa que simplemente no podían.
  2. Durante años, los consumidores ignoraron la calidad de los mensajes de error, por lo que los proveedores también lo hicieron en su mayoría.
  3. Con algún código, el compilador puede volver a sincronizar y diagnosticar errores reales más adelante en el código. Los errores en las plantillas caen en cascada tan mal que casi todo lo anterior es casi inútil.
  4. La flexibilidad general de las plantillas hace que sea difícil adivinar lo que probablemente quiso decir cuando su código tiene un error.
  5. Dentro de una plantilla, el significado de un nombre depende tanto del contexto de la plantilla como del contexto de la instanciación, y la búsqueda dependiente de argumentos puede agregar aún más posibilidades.
  6. La sobrecarga de funciones puede proporcionar muchos candidatos para lo que podría referirse a una llamada de función en particular , y algunos compiladores (por ejemplo, gcc) los enumeran debidamente cuando hay una ambigüedad.
  7. Muchos programadores que nunca considerarían usar parámetros normales sin asegurarse de que los valores pasados ​​cumplan con los requisitos, ni siquiera intentan verificar los parámetros de la plantilla (y tengo que confesar que tiendo a esto yo mismo).

Eso está lejos de ser exhaustivo, pero se entiende la idea general. Incluso si no es fácil, la mayor parte se puede curar. Durante años, le he estado diciendo a la gente que obtenga una copia de Comeau C ++ para uso regular; Probablemente he guardado lo suficiente de un mensaje de error una vez para pagar el compilador. Ahora Clang está llegando al mismo punto (y es aún menos costoso).

Terminaré con una observación general que suena como una broma, pero realmente no lo es. La mayoría de las veces, el verdadero trabajo de un compilador honestamente es convertir el código fuente en mensajes de error. Ya es hora de que los proveedores se concentren en hacer ese trabajo un poco mejor, aunque admitiré abiertamente que cuando escribí compiladores, tuve una fuerte tendencia a tratarlo como secundario (en el mejor de los casos) y en algunos casos casi lo ignoré completamente.

Jerry Coffin
fuente
9

La respuesta simple es, porque Python fue diseñado para funcionar de esa manera, mientras que muchas de las cosas asociadas con las plantillas surgieron por accidente. Nunca tuvo la intención de convertirse en un sistema completo de Turing en sí mismo, por ejemplo. Y si no puede planificar y razonar deliberadamente sobre lo que sucede cuando su sistema funciona , ¿por qué alguien debería esperar una planificación cuidadosa y reflexiva sobre lo que sucede cuando algo sale mal?

Además, como señaló, el intérprete de Python puede hacer que sea mucho más fácil para usted al mostrar un seguimiento de pila porque está interpretando el código de Python. Si un compilador de C ++ detecta un error de plantilla y le da un seguimiento de la pila, eso sería tan inútil como "vomitar plantilla", ¿no?

Mason Wheeler
fuente