Warum druckt cout in diesem Codeausschnitt „2 + 3 = 15“?

126

Warum ist die Ausgabe des folgenden Programms so, wie sie ist?

#include <iostream>
using namespace std;

int main(){

    cout << "2+3 = " <<
    cout << 2 + 3 << endl;
}

produziert

2+3 = 15

anstelle der erwarteten

2+3 = 5

Diese Frage hat bereits mehrere Schließ- / Wiedereröffnungszyklen durchlaufen.

Bitte berücksichtigen Sie diese Metadiskussion zu diesem Thema, bevor Sie zum Abschluss abstimmen .

Hokhy Tann
quelle
96
Sie möchten ein Semikolon ;am Ende der ersten Ausgabezeile, nicht <<. Sie drucken nicht das, was Sie zu drucken glauben. Sie tun cout << cout, was druckt 1(es verwendet cout.operator bool(), denke ich). Dann folgt 5(von 2+3) sofort und lässt es wie die Nummer fünfzehn aussehen.
Igor Tandetnik
5
@StephanLechner Dann wird wahrscheinlich gcc4 verwendet. Sie hatten bis gcc5 keine vollständig kompatiblen Streams, insbesondere hatten sie bis dahin immer noch die implizite Konvertierung.
Baum mit Augen
4
@IgorTandetnik das klingt wie der Anfang einer Antwort. Es scheint viele Feinheiten zu dieser Frage zu geben, die beim ersten Lesen nicht erkennbar sind.
Mark Ransom
14
Warum stimmen die Leute immer wieder ab, um diese Frage zu schließen? Es ist nicht "Bitte sagen Sie mir, was mit diesem Code nicht stimmt", sondern "Warum erzeugt dieser Code diese Ausgabe?" Die Antwort auf die erste lautet "Sie haben einen Tippfehler gemacht", ja, aber die zweite erfordert eine Erklärung, wie der Compiler den Code interpretiert, warum es sich nicht um einen Compilerfehler handelt und wie er "1" anstelle einer Zeigeradresse erhält.
jaggedSpire
6
@jaggedSpire Wenn es sich nicht um einen Tippfehler handelt, ist dies eine sehr schlechte Frage, da dann absichtlich ein ungewöhnliches Konstrukt verwendet wird, das wie ein Tippfehler aussieht, ohne darauf hinzuweisen, dass dies beabsichtigt ist. In jedem Fall verdient eine enge Abstimmung. (Entweder aufgrund eines Tippfehlers oder schlecht / böswillig. Dies ist eine Seite für Leute, die Hilfe suchen, nicht für Leute, die versuchen, andere auszutricksen.)
David Schwartz

Antworten:

229

Ob absichtlich oder versehentlich, Sie haben <<am Ende der ersten Ausgabezeile, wo Sie wahrscheinlich gemeint haben ;. Sie haben also im Wesentlichen

cout << "2+3 = ";  // this, of course, prints "2+3 = "
cout << cout;      // this prints "1"
cout << 2 + 3;     // this prints "5"
cout << endl;      // this finishes the line

Die Frage läuft also darauf hinaus: Warum cout << cout;drucken "1"?

Dies stellt sich vielleicht überraschend subtil heraus. std::coutstellt über seine Basisklasse einen bestimmten Typkonvertierungsoperatorstd::basic_ios bereit , der im booleschen Kontext wie in verwendet werden soll

while (cout) { PrintSomething(cout); }

Dies ist ein ziemlich schlechtes Beispiel, da es schwierig ist, eine Ausgabe zum Scheitern zu bringen - aber std::basic_iostatsächlich eine Basisklasse für Eingabe- und Ausgabestreams ist und für die Eingabe viel sinnvoller ist:

int value;
while (cin >> value) { DoSomethingWith(value); }

(verlässt die Schleife am Ende des Streams oder wenn Stream-Zeichen keine gültige Ganzzahl bilden).

Die genaue Definition dieses Konvertierungsoperators hat sich zwischen den Versionen C ++ 03 und C ++ 11 des Standards geändert. In älteren Versionen wurde es operator void*() const;(normalerweise als return fail() ? NULL : this;) implementiert , während es in neueren Versionen explicit operator bool() const;(normalerweise einfach als return !fail();) implementiert wurde . Beide Deklarationen funktionieren in einem booleschen Kontext einwandfrei, verhalten sich jedoch unterschiedlich, wenn sie außerhalb eines solchen Kontexts (falsch) verwendet werden.

Insbesondere würde nach C ++ 03-Regeln eine Adresse cout << coutinterpretiert cout << cout.operator void*()und gedruckt. Nach C ++ 11-Regeln cout << coutsollte überhaupt nicht kompiliert werden, da der Operator deklariert ist explicitund daher nicht an impliziten Konvertierungen teilnehmen kann. Dies war in der Tat die Hauptmotivation für die Änderung - das Kompilieren von unsinnigem Code zu verhindern. Ein Compiler, der einem der beiden Standards entspricht, würde kein Programm erzeugen, das gedruckt wird "1".

Anscheinend erlauben bestimmte C ++ - Implementierungen das Mischen und Anpassen des Compilers und der Bibliothek auf eine Weise, die zu einem nicht konformen Ergebnis führt (unter Angabe von @StephanLechner: "Ich habe in xcode eine Einstellung gefunden, die 1 ergibt, und eine andere Einstellung, die eine Adresse ergibt: Sprachdialekt c ++ 98 in Kombination mit "Standardbibliothek libc ++ (LLVM-Standardbibliothek mit C ++ 11-Unterstützung)" ergibt 1, während c ++ 98 in Kombination mit libstdc (gnu c ++ Standardbibliothek) eine Adresse ergibt; "). Sie können einen C ++ 03-Compiler, der explicitKonvertierungsoperatoren (die in C ++ 11 neu sind) nicht versteht, mit einer C ++ 11-Bibliothek kombinieren, die die Konvertierung als definiert operator bool(). Mit einer solchen Mischung wird es möglich cout << cout, als interpretiert zu werden cout << cout.operator bool(), was wiederum einfach ist cout << trueund druckt "1".

Igor Tandetnik
quelle
1
@TC Ich bin mir ziemlich sicher, dass es in diesem speziellen Bereich keinen Unterschied zwischen C ++ 03 und C ++ 98 gibt. Ich nehme an, ich könnte alle Erwähnungen von C ++ 03 durch "vor C ++ 11" ersetzen, wenn dies zur Klärung der Angelegenheit beitragen würde. Ich bin mit den Feinheiten der Compiler- und Bibliotheksversionierung unter Linux et al. Überhaupt nicht vertraut. Ich bin ein Windows / MSVC-Typ.
Igor Tandetnik
4
Ich habe nicht versucht, zwischen C ++ 03 und C ++ 98 zu picken. Der Punkt ist, dass libc ++ nur C ++ 11 und neuer ist. Es wird nicht versucht, C ++ 98/03 zu entsprechen.
TC
45

Wie Igor sagt, erhalten Sie dies mit einer C ++ 11-Bibliothek, in std::basic_iosder die operator boolanstelle der operator void*, aber irgendwie nicht deklariert (oder als) behandelt wird explicit. Sehen Sie hier für die richtige Erklärung.

Ein konformer C ++ 11-Compiler liefert beispielsweise das gleiche Ergebnis mit

#include <iostream>
using namespace std;

int main() {
    cout << "2+3 = " << 
    static_cast<bool>(cout) << 2 + 3 << endl;
}

In Ihrem Fall static_cast<bool>wird dies jedoch (fälschlicherweise) als implizite Konvertierung zugelassen.


Bearbeiten: Da dies kein gewöhnliches oder erwartetes Verhalten ist, kann es hilfreich sein, Ihre Plattform, Compilerversion usw. zu kennen.


Bearbeiten 2: Als Referenz wird der Code normalerweise entweder als geschrieben

    cout << "2+3 = "
         << 2 + 3 << endl;

oder als

    cout << "2+3 = ";
    cout << 2 + 3 << endl;

und es ist das Mischen der beiden Stile, die den Fehler aufgedeckt haben.

Nutzlos
quelle
1
Ihr erster Lösungsvorschlag enthält einen Tippfehler. Ein zu viel Operator.
Eerorika
3
Jetzt mache ich es auch, es muss ansteckend sein. Vielen Dank!
Useless
1
Ha! :) Bei der ersten Bearbeitung meiner Antwort schlug ich vor, das Semikolon hinzuzufügen, erkannte aber den Operator am Ende der Zeile nicht. Ich denke, wir haben zusammen mit OP die wichtigsten Permutationen von Tippfehlern generiert, die dies haben kann.
Eerorika
21

Der Grund für die unerwartete Ausgabe ist ein Tippfehler. Du hast es wahrscheinlich gemeint

cout << "2+3 = "
     << 2 + 3 << endl;

Wenn wir die Zeichenfolgen mit der erwarteten Ausgabe ignorieren, bleibt Folgendes übrig:

cout << cout;

Seit C ++ 11 ist dies schlecht geformt. std::coutist nicht implizit in etwas konvertierbar, das std::basic_ostream<char>::operator<<(oder eine Überlastung durch Nichtmitglieder) akzeptieren würde. Daher muss ein standardkonformer Compiler Sie zumindest davor warnen. Mein Compiler hat sich geweigert, Ihr Programm zu kompilieren.

std::coutwäre konvertierbar in boolund die Bool-Überladung des Stream-Eingabeoperators hätte die beobachtete Ausgabe von 1. Diese Überladung ist jedoch explizit und sollte daher keine implizite Konvertierung zulassen. Es scheint, dass Ihre Compiler- / Standardbibliotheksimplementierung nicht genau dem Standard entspricht.

In einem Standard vor C ++ 11 ist dies gut formuliert. Damals std::coutmußte einen impliziten Umwandlungsoperator an void*dem einen Strom - Eingangsoperator Überlastung hat. Die Ausgabe dafür wäre jedoch unterschiedlich. es würde die Speicheradresse des std::coutObjekts drucken .

Eerorika
quelle
11

Der veröffentlichte Code sollte nicht für C ++ 11 (oder einen späteren konformen Compiler) kompiliert werden, er sollte jedoch ohne Warnung bei Implementierungen vor C ++ 11 kompiliert werden.

Der Unterschied besteht darin, dass C ++ 11 die Konvertierung eines Streams in einen Bool explizit vorgenommen hat:

C.2.15 Abschnitt 27: Eingabe- / Ausgabebibliothek [diff.cpp03.input.output] 27.7.2.1.3, 27.7.3.4, 27.5.5.4

Änderung: Geben Sie die Verwendung von explizit in vorhandenen booleschen Konvertierungsoperatoren an.
Begründung: Klären Sie Absichten, vermeiden Sie Problemumgehungen.
Auswirkung auf die ursprüngliche Funktion: Gültiger C ++ 2003-Code, der auf impliziten booleschen Konvertierungen basiert, kann nicht mit diesem internationalen Standard kompiliert werden. Solche Konvertierungen erfolgen unter folgenden Bedingungen:

  • Übergeben eines Werts an eine Funktion, die ein Argument vom Typ bool akzeptiert;
    ...

Der ostream-Operator << wird mit einem bool-Parameter definiert. Da eine Konvertierung in Bool existierte (und nicht explizit war), wurde Pre-C ++ 11 cout << coutübersetzt, in cout << truedas 1 ergibt.

Und gemäß C.2.15 sollte dies ab C ++ 11 nicht mehr kompiliert werden.

Serge Ballesta
quelle
3
In boolC ++ 03 gab es keine Konvertierung nach , es gibt jedoch eine, std::basic_ios::operator void*()die als steuernder Ausdruck einer Bedingung oder Schleife von Bedeutung ist.
Ben Voigt
7

Auf diese Weise können Sie Ihren Code problemlos debuggen. Wenn Sie coutIhre Ausgabe verwenden, wird sie gepuffert, sodass Sie sie folgendermaßen analysieren können:

Stellen Sie sich vor, das erste Auftreten von coutrepräsentiert den Puffer und der Operator <<repräsentiert das Anhängen an das Ende des Puffers. Das Ergebnis des Operators <<ist in Ihrem Fall der Ausgabestream cout. Sie starten von:

cout << "2+3 = " << cout << 2 + 3 << endl;

Nachdem Sie die oben genannten Regeln angewendet haben, erhalten Sie eine Reihe von Aktionen wie folgt:

buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);

Wie ich bereits sagte, ist das Ergebnis von buffer.append()Puffer. Zu Beginn ist Ihr Puffer leer und Sie müssen die folgende Anweisung verarbeiten:

Aussage: buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);

Puffer: empty

Zuerst haben Sie, buffer.append("2+3 = ")die die angegebene Zeichenfolge direkt in den Puffer legt und wird buffer. Jetzt sieht Ihr Staat so aus:

Aussage: buffer.append(cout).append(2 + 3).append(endl);

Puffer: 2+3 = 

Danach analysieren Sie Ihre Aussage weiter und stoßen auf ein coutArgument, das an das Ende des Puffers angehängt werden soll. Das coutwird so behandelt, 1dass Sie 1an das Ende Ihres Puffers anhängen . Jetzt bist du in diesem Zustand:

Aussage: buffer.append(2 + 3).append(endl);

Puffer: 2+3 = 1

Das nächste, was Sie im Puffer haben, ist, 2 + 3und da das Hinzufügen eine höhere Priorität als der Ausgabeoperator hat, fügen Sie zuerst diese beiden Zahlen hinzu und setzen dann das Ergebnis in den Puffer. Danach erhalten Sie:

Aussage: buffer.append(endl);

Puffer: 2+3 = 15

Schließlich fügen Sie endlam Ende des Puffers einen Wert von hinzu und Sie haben:

Aussage:

Puffer: 2+3 = 15\n

Nach diesem Vorgang werden die Zeichen aus dem Puffer einzeln aus dem Puffer in die Standardausgabe gedruckt. Das Ergebnis Ihres Codes ist also 2+3 = 15. Wenn Sie sich das ansehen, erhalten Sie zusätzlich 1von coutIhnen versucht zu drucken. Durch Entfernen << coutaus Ihrer Anweisung erhalten Sie die gewünschte Ausgabe.

Ivan Kulezic
quelle
6
Obwohl dies alles wahr (und wunderschön formatiert) ist, denke ich, dass es die Frage aufwirft. Ich glaube, die Frage läuftcout << cout1 auf "Warum produziert man überhaupt ?" , und Sie haben gerade behauptet, dass dies mitten in einer Diskussion über die Verkettung von Einfügeoperatoren der Fall ist.
Nutzlos
1
+1 für die schöne Formatierung. In Anbetracht dessen, dass dies Ihre erste Antwort ist, ist es schön, dass Sie versuchen zu helfen :)
Gldraphael