Perché cout stampa "2 + 3 = 15" in questo frammento di codice?


126

Perché l'output del programma seguente è quello che è?

#include <iostream>
using namespace std;

int main(){

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

produce

2+3 = 15

invece del previsto

2+3 = 5

Questa domanda ha già superato diversi cicli di chiusura / riapertura.

Prima di votare per chiudere, considera questa meta discussione su questo problema.


96
Vuoi un punto e virgola ;alla fine della prima riga di output, no <<. Non stai stampando ciò che pensi di stampare. Stai facendo cout << cout, che stampa 1(usa cout.operator bool(), credo). Quindi 5(da 2+3) segue immediatamente, facendolo sembrare il numero quindici.
Igor Tandetnik,

5
@StephanLechner Probabilmente sta usando gcc4 allora. Non avevano flussi pienamente conformi fino a quando gcc5, in particolare, avevano ancora la conversione implicita fino ad allora.
Baum mit Augen

4
@IgorTandetnik che sembra l'inizio di una risposta. Sembra che ci siano molte sottigliezze in questa domanda che non sono evidenti in prima lettura.
Mark Ransom,

14
Perché le persone continuano a votare per chiudere questa domanda? Non è "Per favore, dimmi cosa c'è che non va in questo codice", ma "Perché questo codice produce questo output?" La risposta alla prima è "hai fatto un refuso", sì, ma la seconda richiede una spiegazione di come il compilatore sta interpretando il codice, perché non è un errore del compilatore e come sta ottenendo "1" invece di un indirizzo puntatore.
jaggedSpire

6
@jaggedSpire Se non è un errore tipografico, allora è una pessima domanda perché usa deliberatamente un costrutto insolito che sembra un errore tipografico senza sottolineare che è intenzionale. Ad ogni modo, merita un voto attento. (O a causa di un errore tipografico o male / male. Questo è un sito per persone in cerca di aiuto, non persone che cercano di ingannare gli altri.)
David Schwartz,

Risposte:


229

Sia intenzionalmente che per caso, hai <<alla fine della prima linea di uscita, dove probabilmente intendevi ;. Quindi essenzialmente hai

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

Quindi la domanda si riduce a questo: perché cout << cout;stampa "1"?

Questo risulta essere, forse sorprendentemente, sottile. std::cout, tramite la sua classe base std::basic_ios, fornisce un determinato operatore di conversione di tipo che deve essere utilizzato in un contesto booleano, come in

while (cout) { PrintSomething(cout); }

Questo è un esempio piuttosto scarso, poiché è difficile far fallire l'output, ma in std::basic_iosrealtà è una classe di base sia per i flussi di input che di output e per l'input ha molto più senso:

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

(esce dal ciclo alla fine del flusso o quando i caratteri del flusso non formano un numero intero valido).

Ora, la definizione esatta di questo operatore di conversione è cambiata tra le versioni C ++ 03 e C ++ 11 dello standard. Nelle versioni precedenti, era operator void*() const;(tipicamente implementato come return fail() ? NULL : this;), mentre nelle versioni più recenti explicit operator bool() const;(tipicamente implementato semplicemente come return !fail();). Entrambe le dichiarazioni funzionano bene in un contesto booleano, ma si comportano diversamente quando (mis) vengono utilizzate al di fuori di tale contesto.

In particolare, in base alle regole C ++ 03, cout << coutsarebbe interpretato come cout << cout.operator void*()e stampare un indirizzo. Secondo le regole C ++ 11, cout << coutnon dovrebbe essere compilato affatto, poiché l'operatore è dichiarato explicite quindi non può partecipare a conversioni implicite. Questa era in realtà la motivazione principale per il cambiamento, che impediva la compilazione di codice senza senso. Un compilatore conforme a entrambi gli standard non produrrebbe un programma per la stampa "1".

Apparentemente, alcune implementazioni C ++ consentono di mescolare e abbinare il compilatore e la libreria in modo tale da produrre risultati non conformi (citando @StephanLechner: "Ho trovato un'impostazione in xcode che produce 1, e un'altra impostazione che produce un indirizzo: dialetto della lingua c ++ 98 combinato con "Libreria standard libc ++ (libreria standard LLVM con supporto c ++ 11)" produce 1, mentre c ++ 98 combinato con libstdc (libreria standard gnu c ++) fornisce un indirizzo; "). Puoi avere un compilatore in stile C ++ 03 che non comprende gli explicitoperatori di conversione (che sono nuovi in ​​C ++ 11) combinato con una libreria in stile C ++ 11 che definisce la conversione come operator bool(). Con un tale mix, diventa possibile cout << coutessere interpretato come cout << cout.operator bool(), che a sua volta è semplicemente cout << truee stampa "1".


1
@TC Sono abbastanza sicuro che non ci sia differenza tra C ++ 03 e C ++ 98 in questa particolare area. Suppongo che potrei sostituire tutte le menzioni di C ++ 03 con "pre-C ++ 11", se ciò aiutasse a chiarire le cose. Non ho alcuna familiarità con la complessità del versioning di compilatori e librerie su Linux et al; Sono un ragazzo di Windows / MSVC.
Igor Tandetnik,

4
Non stavo cercando di fare nitpick tra C ++ 03 e C ++ 98; il punto è che libc ++ è solo C ++ 11 e più recente; non cerca di conformarsi a C ++ 98/03.
TC

45

Come dice Igor, lo ottieni con una libreria C ++ 11, dove std::basic_iosha operator boolinvece il operator void*, ma in qualche modo non è dichiarato (o trattato come) explicit. Vedi qui per la dichiarazione corretta.

Ad esempio, un compilatore C ++ 11 conforme darà lo stesso risultato con

#include <iostream>
using namespace std;

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

ma nel tuo caso, static_cast<bool>viene (erroneamente) consentito come conversione implicita.


Modifica: poiché questo non è un comportamento normale o previsto, potrebbe essere utile conoscere la tua piattaforma, la versione del compilatore, ecc.


Modifica 2: come riferimento, il codice verrebbe normalmente scritto come

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

o come

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

e sta mescolando i due stili insieme che ha esposto il bug.


1
C'è un errore di battitura nel primo codice soluzione suggerito. Uno di troppi operatori.
Eerorika,

3
Ora lo sto facendo anche io, deve essere contagioso. Grazie!
Inutile

1
Ha! :) Nella modifica iniziale della mia risposta, ho suggerito di aggiungere il punto e virgola, ma non ho realizzato l'operatore alla fine della riga. Penso che insieme a OP, abbiamo generato permutazioni più significative di errori di battitura che questo può avere.
Eerorika,

21

Il motivo dell'output imprevisto è un errore di battitura. Probabilmente intendevi

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

Se ignoriamo le stringhe che hanno l'output previsto, restiamo con:

cout << cout;

Dal C ++ 11, questo è mal formato. std::coutnon è implicitamente convertibile in qualcosa che std::basic_ostream<char>::operator<<(o un sovraccarico non membro) accetterebbe. Pertanto, un compilatore conforme agli standard deve almeno avvisarti di farlo. Il mio compilatore ha rifiutato di compilare il tuo programma.

std::coutsarebbe convertibile in bool, e il sovraccarico bool dell'operatore di input di flusso avrebbe l'output osservato di 1. Tuttavia, tale sovraccarico è esplicito, quindi non dovrebbe consentire una conversione implicita. Sembra che l'implementazione del compilatore / libreria standard non sia strettamente conforme allo standard.

In uno standard pre-C ++ 11, questo è ben formato. Allora std::coutaveva un operatore di conversione implicito a void*cui ha un sovraccarico di operatore di input di flusso. L'output per questo sarebbe comunque diverso. stamperebbe l'indirizzo di memoria std::coutdell'oggetto.


11

Il codice pubblicato non deve essere compilato per nessun C ++ 11 (o compilatore conforme successivo), ma dovrebbe essere compilato senza nemmeno un avviso sulle implementazioni precedenti a C ++ 11.

La differenza è che C ++ 11 ha reso esplicita la conversione di uno stream in un bool:

C.2.15 Clausola 27: Libreria di input / output [diff.cpp03.input.output] 27.7.2.1.3, 27.7.3.4, 27.5.5.4

Modifica: specificare l'uso di espliciti negli operatori di conversione booleani esistenti.
Motivazione: chiarire le intenzioni, evitare soluzioni alternative.
Effetto sulla funzionalità originale: il codice C ++ 2003 valido che si basa su conversioni booleane implicite non verrà compilato con questo standard internazionale. Tali conversioni si verificano nelle seguenti condizioni:

  • passare un valore a una funzione che accetta un argomento di tipo bool;
    ...

L'operatore ostream << è definito con un parametro bool. Poiché esisteva una conversione in bool (e non era esplicita) è pre-C ++ 11, è cout << coutstata tradotta in cout << truecui produce 1.

E secondo C.2.15, questo non dovrebbe più compilare a partire da C ++ 11.


3
Non boolesiste alcuna conversione in C ++ 03, tuttavia esiste std::basic_ios::operator void*()l'espressione di controllo di un condizionale o di un ciclo.
Ben Voigt,

7

Puoi facilmente eseguire il debug del tuo codice in questo modo. Quando si utilizza coutl'output viene bufferizzato in modo da poterlo analizzare in questo modo:

Immagina che la prima occorrenza di coutrappresenti il ​​buffer e che l'operatore <<rappresenti l'aggiunta alla fine del buffer. Il risultato dell'operatore <<è il flusso di output, nel tuo caso cout. Si parte da:

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

Dopo aver applicato le regole sopra indicate ottieni una serie di azioni come questa:

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

Come ho detto prima, il risultato buffer.append()è buffer. All'inizio il buffer è vuoto e devi elaborare la seguente istruzione:

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

buffer: empty

Per prima cosa hai buffer.append("2+3 = ")quale mette la stringa data direttamente nel buffer e diventa buffer. Ora il tuo stato è simile al seguente:

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

buffer: 2+3 = 

Dopo di ciò continui ad analizzare la tua dichiarazione e ti imbatti in un coutargomento da aggiungere alla fine del buffer. Il coutè trattata come 1in modo da aggiungere 1alla fine del buffer. Ora sei in questo stato:

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

buffer: 2+3 = 1

La prossima cosa che hai nel buffer è 2 + 3e poiché l'addizione ha una precedenza più alta rispetto all'operatore di output, prima aggiungi questi due numeri e poi inserirai il risultato nel buffer. Dopo di che ottieni:

dichiarazione: buffer.append(endl);

buffer: 2+3 = 15

Infine aggiungi valore di endlalla fine del buffer e hai:

dichiarazione:

buffer: 2+3 = 15\n

Dopo questo processo, i caratteri dal buffer vengono stampati dal buffer sull'output standard uno alla volta. Quindi il risultato del tuo codice è 2+3 = 15. Se lo guardi, ne ricevi altri 1da quando couthai provato a stampare. Rimuovendo << coutdalla tua dichiarazione otterrai l'output desiderato.


6
Anche se questo è tutto vero (e magnificamente formattato), penso che stia ponendo la domanda. Credo che la domanda si riduce a "Perché in primo luogo cout << coutproduce 1?" e hai appena affermato che lo fa nel mezzo di una discussione sul concatenamento degli operatori di inserimento.
Inutile

1
+1 per la bella formattazione però. Considerando che questa è la tua prima risposta, è bello che tu stia cercando di aiutare :)
Gldraphael,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.