Pourquoi les messages d'erreur du modèle C ++ sont-ils si horribles?

28

Les modèles C ++ sont connus pour générer des messages d'erreur longs et illisibles. J'ai une idée générale de la raison pour laquelle les messages d'erreur de modèle en C ++ sont si mauvais. Essentiellement, le problème est que l'erreur n'est déclenchée que lorsque le compilateur rencontre une syntaxe qui n'est pas prise en charge par un certain type dans un modèle. Par exemple:

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

Si Tne prend pas en charge l' +=opérateur, le compilateur générera un message d'erreur. Et si cela se produit au fond d'une bibliothèque quelque part, le message d'erreur peut contenir des milliers de lignes.

Mais les modèles C ++ ne sont essentiellement qu'un mécanisme de typage de canard au moment de la compilation. Une erreur de modèle C ++ est conceptuellement très similaire à une erreur de type d'exécution qui peut se produire dans un langage dynamique comme Python. Par exemple, considérez le code Python suivant:

def dosomething(x):
   x.foo()

Ici, s'il xn'a pas de foo()méthode, l'interpréteur Python lève une exception et affiche une trace de pile avec un message d'erreur assez clair indiquant le problème. Même si l'erreur n'est déclenchée que lorsque l'interpréteur est au fond d'une fonction de bibliothèque, le message d'erreur d'exécution n'est toujours pas aussi mauvais que le vomissement illisible craché par un compilateur C ++ typique. Alors pourquoi un compilateur C ++ ne peut-il pas être plus clair sur ce qui ne va pas? Pourquoi certains messages d'erreur du modèle C ++ font-ils littéralement défiler la fenêtre de ma console pendant plus de 5 secondes?

Channel72
la source
6
Certains compilateurs ont des messages d'erreur horribles, mais d'autres sont vraiment bons ( clang++wink wink).
Benjamin Bannier
2
Vous préférez donc que vos programmes échouent au moment de l'exécution, soient livrés aux mains d'un client plutôt que d'échouer au moment de la compilation?
P Shved
13
@ Pavel, non. Cette question ne concerne pas les avantages / inconvénients de la vérification des erreurs d'exécution par rapport à la compilation.
Channel72
1
Comme exemple d'erreurs de modèle C ++ volumineuses, FWIW: codegolf.stackexchange.com/a/10470/7174
kebs

Réponses:

28

Les messages d'erreur de modèle peuvent être notoires, mais ne sont en aucun cas toujours longs et illisibles. Dans ce cas, l'intégralité du message d'erreur (de gcc) est:

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

Comme dans votre exemple Python, vous obtenez une «trace de pile» de points d'instanciation de modèle et un message d'erreur clair indiquant le problème.

Parfois, les messages d'erreur liés aux modèles peuvent être beaucoup plus longs, pour diverses raisons:

  • La "trace de pile" pourrait être beaucoup plus profonde
  • Les noms de type peuvent être beaucoup plus longs, car les modèles sont instanciés avec d'autres instanciations de modèle comme arguments, et affichés avec tous leurs qualificatifs d'espace de noms
  • Lorsque la résolution de surcharge échoue, le message d'erreur peut contenir une liste de surcharges candidates (qui peuvent chacune contenir des noms de type très longs)
  • La même erreur peut être signalée plusieurs fois, si un modèle non valide est instancié à plusieurs endroits

La principale différence avec Python est le système de type statique, ce qui conduit à la nécessité d'inclure les noms de type (parfois longs) dans le message d'erreur. Sans eux, il serait parfois très difficile de diagnostiquer pourquoi la résolution de surcharge a échoué. Avec eux, votre défi n'est plus de deviner où est le problème, mais de déchiffrer les hiéroglyphes qui vous indiquent où il se trouve.

De plus, la vérification au moment de l'exécution signifie que le programme s'arrêtera à la première erreur qu'il rencontre, affichant uniquement un seul message. Un compilateur peut afficher toutes les erreurs qu'il rencontre jusqu'à ce qu'il abandonne; au moins en C ++, il ne doit pas s'arrêter sur la première erreur du fichier, car cela peut être la conséquence d'une erreur ultérieure.

Mike Seymour
la source
4
Pourriez-vous donner un exemple d'une erreur résultant d'une erreur ultérieure?
Ruslan
12

Voici quelques-unes des raisons évidentes:

  1. Histoire. Lorsque gcc, MSVC, etc., étaient nouveaux, ils ne pouvaient pas se permettre d'utiliser beaucoup d'espace supplémentaire pour stocker des données afin de produire de meilleurs messages d'erreur. La mémoire était assez rare pour qu'ils ne puissent tout simplement pas.
  2. Pendant des années, les consommateurs ont ignoré la qualité des messages d'erreur, donc les fournisseurs l'ont également fait.
  3. Avec du code, le compilateur peut resynchroniser et diagnostiquer les erreurs réelles plus tard dans le code. Les erreurs dans les modèles tombent en cascade si mal que tout ce qui est passé le premier est presque toujours inutile.
  4. La flexibilité générale des modèles rend difficile de deviner ce que vous vouliez probablement dire lorsque votre code contient une erreur.
  5. À l'intérieur d'un modèle, la signification d'un nom dépend à la fois du contexte du modèle et du contexte de l'instanciation, et la recherche dépendante des arguments peut ajouter encore plus de possibilités.
  6. La surcharge de fonctions peut fournir de nombreux candidats à ce à quoi un appel de fonction particulier pourrait se référer, et certains compilateurs (par exemple, gcc) les répertorient consciencieusement en cas d'ambiguïté.
  7. De nombreux codeurs qui n'envisageraient jamais d'utiliser des paramètres normaux sans s'assurer que les valeurs transmises répondent aux exigences, n'essaient même pas de vérifier les paramètres du modèle (et je dois l'avouer, j'ai tendance à le faire moi-même).

C'est loin d'être exhaustif, mais vous avez une idée générale. Même si ce n'est pas facile, la majeure partie peut être guérie. Depuis des années, je dis aux gens de se procurer une copie de Comeau C ++ pour une utilisation régulière; J'ai probablement économisé assez d'un message d'erreur une fois pour payer le compilateur. Maintenant, Clang arrive au même point (et c'est encore moins cher).

Je terminerai par une observation générale qui ressemble à une blague, mais qui ne l'est vraiment pas. La plupart du temps, vrai travail d'un compilateur honnêtement est de transformer le code source dans les messages d'erreur. Il est grand temps que les fournisseurs se concentrent un peu mieux sur ce travail - même si je reconnais ouvertement que lorsque j'ai écrit des compilateurs, j'avais une forte tendance à le traiter comme secondaire (au mieux) et dans certains cas, je l'ai presque ignoré complètement.

Jerry Coffin
la source
9

La réponse est simple, car Python a été conçu pour fonctionner de cette façon, alors que la plupart des éléments associés aux modèles sont arrivés par accident. Il n'a jamais été destiné à devenir un système complet de Turing en soi, par exemple. Et si vous ne pouvez pas délibérément planifier et raisonner sur ce qui se passe lorsque votre système fonctionne , pourquoi devrait-on s'attendre à une planification minutieuse et réfléchie de ce qui se passe en cas de problème?

De plus, comme vous l'avez souligné, l'interpréteur Python peut vous faciliter la tâche en affichant une trace de pile car il interprète le code Python. Si un compilateur C ++ rencontre une erreur de modèle et vous donne une trace de pile, ce serait tout aussi inutile que "vomir le modèle", n'est-ce pas?

Mason Wheeler
la source