Warum sind Fehlermeldungen in C ++ - Vorlagen so schrecklich?

28

C ++ - Vorlagen sind dafür berüchtigt, lange, unlesbare Fehlermeldungen zu generieren. Ich habe eine allgemeine Vorstellung davon, warum Vorlagenfehlermeldungen in C ++ so schlecht sind. Das Problem besteht im Wesentlichen darin, dass der Fehler erst ausgelöst wird, wenn der Compiler auf eine Syntax stößt, die von einem bestimmten Typ in einer Vorlage nicht unterstützt wird. Beispielsweise:

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

Wenn Tder +=Operator nicht unterstützt wird, generiert der Compiler eine Fehlermeldung. Und wenn dies irgendwo tief in einer Bibliothek passiert, kann die Fehlermeldung Tausende von Zeilen lang sein.

C ++ - Templates sind jedoch im Wesentlichen nur ein Mechanismus für die Eingabe von Daten während der Kompilierung. Ein C ++ - Vorlagenfehler ist konzeptionell einem Laufzeitfehler sehr ähnlich, der in einer dynamischen Sprache wie Python auftreten kann. Betrachten Sie beispielsweise den folgenden Python-Code:

def dosomething(x):
   x.foo()

Wenn xhier keine foo()Methode vorhanden ist, löst der Python-Interpreter eine Ausnahme aus und zeigt eine Stapelverfolgung zusammen mit einer ziemlich klaren Fehlermeldung an, die auf das Problem hinweist. Auch wenn der Fehler erst ausgelöst wird, wenn sich der Interpreter tief in einer Bibliotheksfunktion befindet, ist die Laufzeitfehlermeldung bei weitem nicht so schlimm wie das unlesbare Erbrochene, das von einem typischen C ++ - Compiler ausgegeben wird. Warum kann ein C ++ - Compiler nicht klarer sagen, was schief gelaufen ist? Warum führen einige Fehlermeldungen in C ++ - Vorlagen dazu, dass mein Konsolenfenster länger als 5 Sekunden gescrollt wird?

Channel72
quelle
6
Einige Compiler haben schreckliche Fehlermeldungen, andere sind wirklich gut ( clang++wink wink).
Benjamin Bannier
2
Sie würden es also vorziehen, wenn Ihre Programme zur Laufzeit fehlschlagen, von einem Kunden ausgeliefert werden, anstatt zur Kompilierungszeit fehlzuschlagen?
P Shved
13
@Pavel, nein. Bei dieser Frage geht es nicht um die Vor- / Nachteile der Laufzeit im Vergleich zur Fehlerprüfung zur Kompilierungszeit.
Channel72
1
Ein Beispiel für große C ++ - Vorlagenfehler ist FWIW: codegolf.stackexchange.com/a/10470/7174
kebs

Antworten:

28

Vorlagenfehlermeldungen sind notorisch, aber keineswegs immer lang und unlesbar. In diesem Fall lautet die gesamte Fehlermeldung (von gcc):

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

Wie in Ihrem Python-Beispiel erhalten Sie eine "Stapelverfolgung" von Vorlageninstanziierungspunkten und eine eindeutige Fehlermeldung, die auf das Problem hinweist.

Manchmal können vorlagenbezogene Fehlermeldungen aus verschiedenen Gründen viel länger werden:

  • Die "Stapelverfolgung" könnte viel tiefer liegen
  • Die Typnamen können viel länger sein, da Vorlagen mit anderen Vorlageninstanziierungen als Argumente instanziiert und mit allen ihren Namespace-Qualifikatoren angezeigt werden
  • Wenn die Überladungsauflösung fehlschlägt, enthält die Fehlermeldung möglicherweise eine Liste der möglichen Überladungen (die jeweils einige sehr lange Typnamen enthalten können).
  • Der gleiche Fehler kann mehrmals gemeldet werden, wenn an vielen Stellen eine ungültige Vorlage instanziiert wird

Der Hauptunterschied zu Python ist das statische Typsystem, was dazu führt, dass die (manchmal langen) Typnamen in die Fehlermeldung aufgenommen werden müssen. Ohne sie wäre es manchmal sehr schwierig zu diagnostizieren, warum die Überlastungslösung fehlgeschlagen ist. Mit ihnen besteht Ihre Herausforderung nicht mehr darin, zu erraten, wo das Problem liegt, sondern die Hieroglyphen zu entziffern, aus denen hervorgeht, wo es sich befindet.

Die Überprüfung zur Laufzeit bedeutet auch, dass das Programm beim ersten aufgetretenen Fehler stoppt und nur eine einzige Meldung anzeigt. Ein Compiler zeigt möglicherweise alle aufgetretenen Fehler an, bis er aufgibt. Zumindest in C ++ sollte es nicht beim ersten Fehler in der Datei anhalten, da dies eine Folge eines späteren Fehlers sein kann.

Mike Seymour
quelle
4
Können Sie ein Beispiel für einen Fehler nennen, der auf einen späteren Fehler zurückzuführen ist?
Ruslan
12

Einige der offensichtlichen Gründe sind:

  1. Geschichte. Als gcc, MSVC usw. neu waren, konnten sie es sich nicht leisten, viel zusätzlichen Speicherplatz zum Speichern von Daten zu verwenden, um bessere Fehlermeldungen zu erzeugen. Der Speicher war knapp genug, dass sie einfach nicht konnten.
  2. Jahrelang haben die Verbraucher die Qualität von Fehlermeldungen ignoriert, und die Hersteller meistens auch.
  3. Mit etwas Code kann der Compiler reale Fehler später im Code neu synchronisieren und diagnostizieren. Fehler in Vorlagen kaskadieren so stark, dass alles, was über das erste hinausgeht, fast immer nutzlos ist.
  4. Die allgemeine Flexibilität von Vorlagen macht es schwierig zu erraten, was Sie wahrscheinlich gemeint haben, wenn Ihr Code einen Fehler aufweist.
  5. Innerhalb einer Vorlage hängt die Bedeutung eines Namens sowohl vom Kontext der Vorlage als auch vom Kontext der Instanziierung ab, und die argumentabhängige Suche kann noch mehr Möglichkeiten hinzufügen.
  6. Funktionsüberladung kann viele Kandidaten für das liefern, worauf sich ein bestimmter Funktionsaufruf beziehen könnte , und einige Compiler (z. B. gcc) listen sie alle pflichtbewusst auf, wenn Unklarheiten bestehen.
  7. Viele Programmierer, die niemals die Verwendung normaler Parameter in Betracht ziehen würden, ohne sicherzustellen, dass die übergebenen Werte den Anforderungen entsprechen, versuchen nicht einmal, Vorlagenparameter zu überprüfen (und ich muss gestehen, ich tendiere dazu selbst).

Das ist alles andere als erschöpfend, aber Sie haben eine allgemeine Vorstellung. Auch wenn es nicht einfach ist, kann das meiste davon geheilt werden. Seit Jahren fordere ich die Leute auf, eine Kopie von Comeau C ++ zur regelmäßigen Verwendung zu erhalten. Ich habe wahrscheinlich genug von einer Fehlermeldung einmal gespeichert, um für den Compiler zu bezahlen. Jetzt kommt Clang zum selben Punkt (und es ist noch billiger).

Ich werde mit einer allgemeinen Bemerkung schließen, die sich wie ein Witz anhört, aber wirklich nicht so ist. Die meiste Zeit, ein richtiger Job des Compilers ehrlich ist Quellcode in Fehlermeldungen zu drehen. Es ist höchste Zeit, dass sich die Anbieter darauf konzentrieren, diesen Job ein bisschen besser zu machen - obwohl ich offen zugeben werde, dass ich beim Schreiben von Compilern eine starke Tendenz hatte, ihn als zweitrangig zu behandeln (bestenfalls) und ihn in einigen Fällen fast ignorierte vollständig.

Jerry Sarg
quelle
9

Die einfache Antwort ist, dass Python so entworfen wurde, dass es so funktioniert, während viele Dinge, die mit Vorlagen verbunden sind, zufällig entstanden sind. Es war zum Beispiel nie beabsichtigt, ein Turing-vollständiges System für sich zu werden. Und wenn Sie nicht absichtlich planen und überlegen können, was passiert, wenn Ihr System funktioniert , warum sollte dann jemand mit einer sorgfältigen und durchdachten Planung rechnen, was passiert, wenn etwas schief geht?

Wie Sie bereits betont haben, kann der Python-Interpreter die Anzeige eines Stack-Trace erheblich vereinfachen, da er Python-Code interpretiert. Wenn ein C ++ - Compiler auf einen Template-Fehler stößt und Ihnen einen Stack-Trace liefert, wäre das genauso wenig hilfreich wie "Template-Erbrochenes", nicht wahr?

Mason Wheeler
quelle