¿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.
;
al final de la primera línea de salida, no<<
. No está imprimiendo lo que cree que está imprimiendo. Lo estás haciendocout << cout
, que imprime1
(usacout.operator bool()
, creo). Luego5
(de2+3
) sigue inmediatamente, haciéndolo parecer el número quince.Respuestas:
Ya sea intencionalmente o por accidente, tiene
<<
al final de la primera línea de salida, donde probablemente quiso decir;
. Entonces esencialmente tienesEntonces 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 basestd::basic_ios
, proporciona un operador de conversión de cierto tipo que está destinado a ser utilizado en contexto booleano, como enEste es un ejemplo bastante pobre, ya que es difícil hacer que la salida falle, pero en
std::basic_ios
realidad es una clase base tanto para flujos de entrada como de salida, y para la entrada tiene mucho más sentido:(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 comoreturn fail() ? NULL : this;
), mientras que en las más nuevas esexplicit operator bool() const;
(típicamente implementado simplemente comoreturn !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 << cout
se interpretaría comocout << cout.operator void*()
e imprimiría alguna dirección. Según las reglas de C ++ 11,cout << cout
no debe compilarse en absoluto, ya que el operador está declaradoexplicit
y, 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
explicit
operadores de conversión (que son nuevos en C ++ 11) combinados con una biblioteca de estilo C ++ 11 que define la conversión comooperator bool()
. Con tal mezcla, es posiblecout << cout
que se interprete comocout << cout.operator bool()
, lo que a su vez es simplecout << true
e imprime"1"
.fuente
Como dice Igor, obtienes esto con una biblioteca C ++ 11, donde
std::basic_ios
tiene el enoperator bool
lugar deloperator 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
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
o como
y está mezclando los dos estilos juntos lo que expuso el error.
fuente
La razón de la salida inesperada es un error tipográfico. Probablemente quisiste decir
Si ignoramos las cadenas que tienen el resultado esperado, nos quedamos con:
Desde C ++ 11, esto está mal formado.
std::cout
no es implícitamente convertible a nada questd::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::cout
sería convertible abool
, 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::cout
tenía un operador de conversión implícito alvoid*
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 delstd::cout
objeto.fuente
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:
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 << cout
se tradujo a locout << true
que produce 1.Y de acuerdo con C.2.15, esto ya no debería compilarse comenzando con C ++ 11.
fuente
bool
existía ninguna conversión en C ++ 03, sin embargo, existe unastd::basic_ios::operator void*()
que es significativa como la expresión controladora de un condicional o un bucle.Puede depurar fácilmente su código de esta manera. Cuando usa
cout
su salida está almacenada para que pueda analizarla así:Imagine que la primera aparición de
cout
representa el búfer y el operador<<
representa anexar al final del búfer. El resultado del operador<<
es el flujo de salida, en su casocout
. 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 conviertebuffer
. 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
cout
como un argumento para agregar al final del búfer. Elcout
se trata como1
si fuera1
el 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 + 3
y 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
endl
al 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ás1
de locout
que intentó imprimir. Al eliminar<< cout
de su declaración obtendrá el resultado deseado.fuente
cout << cout
produce1
en primer lugar?" , y acaba de afirmar que lo hace en medio de una discusión sobre el encadenamiento del operador de inserción.