¿Por qué cout imprime "2 + 3 = 15" en este fragmento de código?

126

¿Por qué es la salida del siguiente programa lo que es?

#include <iostream>
using namespace std;

int main(){

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

produce

2+3 = 15

en lugar de lo esperado

2+3 = 5

Esta pregunta ya ha pasado varios ciclos de cierre / reapertura.

Antes de votar para cerrar, considere esta meta discusión sobre este tema.

Hokhy Tann
fuente
96
Desea punto y coma ;al final de la primera línea de salida, no <<. No está imprimiendo lo que cree que está imprimiendo. Lo estás haciendo cout << cout, que imprime 1(usa cout.operator bool(), creo). Luego 5(de 2+3) sigue inmediatamente, haciéndolo parecer el número quince.
Igor Tandetnik
55
@StephanLechner Probablemente esté usando gcc4 entonces. No tenían flujos totalmente compatibles hasta gcc5, en particular, todavía tenían la conversión implícita hasta entonces.
Baum mit Augen
44
@IgorTandetnik que suena como el comienzo de una respuesta. Parece que hay muchas sutilezas en esta pregunta que no son evidentes en la primera lectura.
Mark Ransom
14
¿Por qué la gente sigue votando para cerrar esta pregunta? No es "Por favor, dime qué tiene de malo este código", sino "¿Por qué este código produce esta salida?" La respuesta a la primera es "cometió un error tipográfico", sí, pero la segunda requiere una explicación de cómo el compilador está interpretando el código, por qué no es un error del compilador y cómo está obteniendo "1" en lugar de una dirección de puntero.
jaggedSpire
66
@jaggedSpire Si no se trata de un error tipográfico, entonces es una pregunta muy mala porque deliberadamente usa una construcción inusual que parece un error tipográfico sin señalar que es intencional. De cualquier manera, merece un voto cercano. (Como debido a un error tipográfico o malo / malicioso. Este es un sitio para personas que buscan ayuda, no para personas que intentan engañar a otros.)
David Schwartz

Respuestas:

229

Ya sea intencionalmente o por accidente, tiene <<al final de la primera línea de salida, donde probablemente quiso decir ;. Entonces esencialmente tienes

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

Entonces la pregunta se reduce a esto: ¿por qué cout << cout;imprimir "1"?

Esto resulta ser, quizás sorprendentemente, sutil. std::cout, a través de su clase base std::basic_ios, proporciona un operador de conversión de cierto tipo que está destinado a ser utilizado en contexto booleano, como en

while (cout) { PrintSomething(cout); }

Este es un ejemplo bastante pobre, ya que es difícil hacer que la salida falle, pero en std::basic_iosrealidad es una clase base tanto para flujos de entrada como de salida, y para la entrada tiene mucho más sentido:

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

(sale del bucle al final de la secuencia, o cuando los caracteres de la secuencia no forman un número entero válido).

Ahora, la definición exacta de este operador de conversión ha cambiado entre las versiones C ++ 03 y C ++ 11 del estándar. En versiones anteriores, era operator void*() const;(típicamente implementado como return fail() ? NULL : this;), mientras que en las más nuevas es explicit operator bool() const;(típicamente implementado simplemente como return !fail();). Ambas declaraciones funcionan bien en un contexto booleano, pero se comportan de manera diferente cuando (mal) se usan fuera de dicho contexto.

En particular, bajo las reglas de C ++ 03, cout << coutse interpretaría como cout << cout.operator void*()e imprimiría alguna dirección. Según las reglas de C ++ 11, cout << coutno debe compilarse en absoluto, ya que el operador está declarado explicity, por lo tanto, no puede participar en conversiones implícitas. De hecho, esa fue la principal motivación para el cambio: evitar que se compilara código sin sentido. Un compilador que cumpla con cualquiera de los estándares no produciría un programa que imprima "1".

Aparentemente, ciertas implementaciones de C ++ permiten mezclar y combinar el compilador y la biblioteca de tal manera que produzca un resultado no conforme (citando @StephanLechner: "Encontré una configuración en xcode que produce 1, y otra configuración que produce una dirección: Dialecto del idioma c ++ 98 combinado con "Biblioteca estándar libc ++ (biblioteca estándar LLVM con soporte para c ++ 11)" produce 1, mientras que c ++ 98 combinado con libstdc (biblioteca estándar gnu c ++) produce una dirección; "). Puede tener un compilador de estilo C ++ 03 que no entienda los explicitoperadores de conversión (que son nuevos en C ++ 11) combinados con una biblioteca de estilo C ++ 11 que define la conversión como operator bool(). Con tal mezcla, es posible cout << coutque se interprete como cout << cout.operator bool(), lo que a su vez es simple cout << truee imprime "1".

Igor Tandetnik
fuente
1
@TC Estoy bastante seguro de que no hay diferencia entre C ++ 03 y C ++ 98 en esta área en particular. Supongo que podría reemplazar todas las menciones de C ++ 03 con "pre-C ++ 11", si esto ayudara a aclarar las cosas. No estoy familiarizado con las complejidades de las versiones de compilador y biblioteca en Linux et al; Soy un chico de Windows / MSVC.
Igor Tandetnik
44
No estaba tratando de seleccionar entre C ++ 03 y C ++ 98; el punto es que libc ++ es C ++ 11 y más reciente solamente; no intenta ajustarse a C ++ 98/03.
TC
45

Como dice Igor, obtienes esto con una biblioteca C ++ 11, donde std::basic_iostiene el en operator boollugar del operator void*, pero de alguna manera no se declara (o trata como) explicit. Vea aquí para la declaración correcta.

Por ejemplo, un compilador C ++ 11 conforme dará el mismo resultado con

#include <iostream>
using namespace std;

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

pero en su caso, static_cast<bool>se está permitiendo (erróneamente) como una conversión implícita.


Editar: dado que esto no es un comportamiento habitual o esperado, puede ser útil conocer su plataforma, versión del compilador, etc.


Edición 2: para referencia, el código generalmente se escribiría como

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

o como

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

y está mezclando los dos estilos juntos lo que expuso el error.

Inútil
fuente
1
Hay un error tipográfico en su primer código de solución sugerido. Demasiado operador.
Eerorika
3
Ahora también lo estoy haciendo, debe ser contagioso. ¡Gracias!
Inútil
1
¡Decir ah! :) En la edición inicial de mi respuesta, sugerí agregar el punto y coma, pero no me di cuenta del operador al final de la línea. Creo que junto con OP, hemos generado las permutaciones más significativas de errores tipográficos que esto puede tener.
eerorika
21

La razón de la salida inesperada es un error tipográfico. Probablemente quisiste decir

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

Si ignoramos las cadenas que tienen el resultado esperado, nos quedamos con:

cout << cout;

Desde C ++ 11, esto está mal formado. std::coutno es implícitamente convertible a nada que std::basic_ostream<char>::operator<<(o una sobrecarga que no sea miembro) aceptaría. Por lo tanto, un compilador conforme a los estándares debe al menos advertirte de esto. Mi compilador se negó a compilar su programa.

std::coutsería convertible a bool, y la sobrecarga de bool del operador de entrada de flujo tendría la salida observada de 1. Sin embargo, esa sobrecarga es explícita, por lo que no debería permitir una conversión implícita. Parece que la implementación de su compilador / biblioteca estándar no se ajusta estrictamente al estándar.

En un estándar anterior a C ++ 11, esto está bien formado. En aquel entonces std::couttenía un operador de conversión implícito al void*que tiene una sobrecarga de operador de entrada de flujo. Sin embargo, la salida para eso sería diferente. imprimiría la dirección de memoria del std::coutobjeto.

eerorika
fuente
11

El código publicado no debe compilarse para ningún C ++ 11 (o compilador conforme posterior), pero debe compilarse sin siquiera una advertencia sobre implementaciones previas de C ++ 11.

La diferencia es que C ++ 11 hizo explícita la conversión de una secuencia en un bool:

C.2.15 Cláusula 27: Biblioteca de entrada / salida [diff.cpp03.input.output] 27.7.2.1.3, 27.7.3.4, 27.5.5.4

Cambio: especifique el uso de explícito en los operadores de conversión booleanos existentes
Justificación: aclare las intenciones, evite soluciones alternativas.
Efecto sobre la característica original: el código válido de C ++ 2003 que se basa en conversiones booleanas implícitas no se compilará con esta Norma Internacional. Dichas conversiones ocurren en las siguientes condiciones:

  • pasar un valor a una función que toma un argumento de tipo bool;
    ...

El operador ostream << se define con un parámetro bool. Como existía una conversión a bool (y no era explícita) es anterior a C ++ 11, cout << coutse tradujo a lo cout << trueque produce 1.

Y de acuerdo con C.2.15, esto ya no debería compilarse comenzando con C ++ 11.

Serge Ballesta
fuente
3
No boolexistía ninguna conversión en C ++ 03, sin embargo, existe una std::basic_ios::operator void*()que es significativa como la expresión controladora de un condicional o un bucle.
Ben Voigt
7

Puede depurar fácilmente su código de esta manera. Cuando usa coutsu salida está almacenada para que pueda analizarla así:

Imagine que la primera aparición de coutrepresenta el búfer y el operador <<representa anexar al final del búfer. El resultado del operador <<es el flujo de salida, en su caso cout. Empiezas desde:

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

Después de aplicar las reglas mencionadas anteriormente, obtiene un conjunto de acciones como esta:

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

Como dije antes, el resultado de buffer.append()es buffer. Al principio, su búfer está vacío y tiene que procesar la siguiente declaración:

declaración: buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);

buffer: empty

Primero tiene buffer.append("2+3 = ")que pone la cadena dada directamente en el búfer y se convierte buffer. Ahora su estado se ve así:

declaración: buffer.append(cout).append(2 + 3).append(endl);

buffer: 2+3 = 

Después de eso, continúa analizando su declaración y aparece coutcomo un argumento para agregar al final del búfer. El coutse trata como 1si fuera 1el final de su búfer. Ahora estás en este estado:

declaración: buffer.append(2 + 3).append(endl);

buffer: 2+3 = 1

Lo siguiente que tiene en el búfer es 2 + 3y dado que la suma tiene mayor prioridad que el operador de salida, primero agregará estos dos números y luego colocará el resultado en el búfer. Después de eso obtienes:

declaración: buffer.append(endl);

buffer: 2+3 = 15

Finalmente agrega valor de endlal final del búfer y tiene:

declaración:

buffer: 2+3 = 15\n

Después de este proceso, los caracteres del búfer se imprimen desde el búfer a la salida estándar uno por uno. Entonces el resultado de su código es 2+3 = 15. Si observa esto, obtendrá más 1de lo coutque intentó imprimir. Al eliminar << coutde su declaración obtendrá el resultado deseado.

Ivan Kulezic
fuente
66
Aunque todo esto es cierto (y muy bien formateado), creo que está planteando la pregunta. Creo que la pregunta se reduce a "¿Por qué cout << coutproduce 1en primer lugar?" , y acaba de afirmar que lo hace en medio de una discusión sobre el encadenamiento del operador de inserción.
Inútil el
1
+1 para el hermoso formato sin embargo. Teniendo en cuenta que esta es su primera respuesta, es bueno que esté tratando de ayudar :)
gldraphael