Por que cout imprime "2 + 3 = 15" neste trecho de código?


126

Por que a saída do programa abaixo é o que é?

#include <iostream>
using namespace std;

int main(){

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

produz

2+3 = 15

em vez do esperado

2+3 = 5

Esta questão já passou por vários ciclos de fechamento / reabertura.

Antes de votar para fechar, considere esta meta-discussão sobre esse problema.


96
Você deseja ponto ;- e - vírgula no final da primeira linha de saída, não <<. Você não está imprimindo o que pensa que está imprimindo. Você está fazendo cout << cout, que imprime 1(usa cout.operator bool(), eu acho). Então 5(de 2+3) segue imediatamente, fazendo com que pareça o número quinze.
Igor Tandetnik

5
@StephanLechner Isso provavelmente está usando o gcc4 então. Eles não tinham fluxos totalmente compatíveis até o gcc5, em particular, ainda tinham a conversão implícita até então.
Baum mit Augen

4
@IgorTandetnik que soa como o início de uma resposta. Parece haver muitas sutilezas nessa pergunta que não são evidentes na primeira leitura.
Mark Ransom

14
Por que as pessoas continuam votando para encerrar esta pergunta? Não é "Por favor, diga-me o que há de errado com este código", mas "Por que esse código produz essa saída?" A resposta para a primeira é "você digitou um erro de digitação", sim, mas a segunda exige uma explicação de como o compilador está interpretando o código, por que não é um erro do compilador e como está recebendo "1" em vez de um endereço de ponteiro.
precisa saber é o seguinte

6
@jaggedSpire Se não é um erro tipográfico, é uma pergunta muito ruim, porque usa deliberadamente uma construção incomum que se parece com um erro tipográfico sem indicar que é intencional. De qualquer maneira, merece uma votação apertada. (. Como quer devido a um erro de digitação ou ruim / malicioso Este é um site para pessoas que procuram ajuda, não pessoas tentando enganar os outros.)
David Schwartz

Respostas:


229

Intencionalmente ou por acidente, você tem <<no final da primeira linha de saída, onde você provavelmente quis dizer ;. Então você essencialmente tem

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

Portanto, a questão se resume a isso: por que cout << cout;imprimir "1"?

Isso acaba sendo, talvez surpreendentemente, sutil. std::cout, por meio de sua classe base std::basic_ios, fornece um certo operador de conversão de tipo que deve ser usado no contexto booleano, como em

while (cout) { PrintSomething(cout); }

Este é um exemplo muito ruim, pois é difícil fazer com que a saída falhe - mas std::basic_iosna verdade é uma classe base para os fluxos de entrada e saída e, para entrada, faz muito mais sentido:

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

(sai do loop no final do fluxo ou quando os caracteres do fluxo não formam um número inteiro válido).

Agora, a definição exata desse operador de conversão mudou entre as versões C ++ 03 e C ++ 11 do padrão. Nas versões mais antigas, era operator void*() const;(normalmente implementado como return fail() ? NULL : this;), enquanto nas mais recentes era explicit operator bool() const;(normalmente implementado simplesmente como return !fail();). Ambas as declarações funcionam bem em um contexto booleano, mas se comportam de maneira diferente quando (mis) usadas fora desse contexto.

Em particular, sob as regras do C ++ 03, cout << coutseria interpretado como cout << cout.operator void*()e impresso algum endereço. Sob as regras do C ++ 11, cout << coutnão deve ser compilado, pois o operador é declarado explicite, portanto, não pode participar de conversões implícitas. Essa foi, de fato, a principal motivação para a mudança - impedindo a compilação de códigos sem sentido. Um compilador que esteja em conformidade com qualquer padrão não produzirá um programa que imprima "1".

Aparentemente, certas implementações em C ++ permitem misturar e combinar o compilador e a biblioteca de uma maneira que produza resultados não conformes (citando @StephanLechner: "Encontrei uma configuração no xcode que produz 1 e outra que gera um endereço: Language dialect c ++ 98 combinado com "Biblioteca padrão libc ++ (biblioteca padrão LLVM com suporte a c ++ 11)" gera 1, enquanto c ++ 98 combinado com libstdc (biblioteca padrão gnu c ++) gera um endereço; "). Você pode ter um compilador no estilo C ++ 03 que não entenda os explicitoperadores de conversão (novos no C ++ 11) combinados com uma biblioteca no estilo C ++ 11 que define a conversão como operator bool(). Com essa mistura, torna-se possível cout << coutinterpretar como cout << cout.operator bool(), que por sua vez é simples cout << truee imprime "1".


1
@TC Tenho certeza de que não há diferença entre C ++ 03 e C ++ 98 nessa área em particular. Suponho que poderia substituir todas as menções do C ++ 03 por "pré-C ++ 11", se isso ajudasse a esclarecer as questões. Não estou familiarizado com os meandros do versionamento de compilador e biblioteca no Linux et al; Eu sou um cara do Windows / MSVC.
Igor Tandetnik

4
Eu não estava tentando escolher entre C ++ 03 e C ++ 98; o ponto é que libc ++ é C ++ 11 e apenas mais recente; ele não tenta estar em conformidade com o C ++ 98/03.
TC

45

Como Igor diz, você obtém isso com uma biblioteca C ++ 11, onde std::basic_iostem o operator boolinvés de operator void*, mas de alguma forma não é declarado (ou tratado como) explicit. Veja aqui a declaração correta.

Por exemplo, um compilador C ++ 11 em conformidade fornecerá o mesmo resultado com

#include <iostream>
using namespace std;

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

mas no seu caso, isso static_cast<bool>está sendo (erroneamente) permitido como uma conversão implícita.


Editar: como esse comportamento não é usual ou esperado, pode ser útil conhecer sua plataforma, versão do compilador etc.


Edit 2: Para referência, o código normalmente seria escrito como

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

ou como

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

e está misturando os dois estilos que expuseram o bug.


1
Há um erro de digitação no seu primeiro código de solução sugerido. Um operador demais.
eerorika

3
Agora também estou fazendo isso, deve ser contagioso. Obrigado!
Inutil)

1
Ha! :) Na edição inicial da minha resposta, sugeri adicionar o ponto-e-vírgula, mas não percebi o operador no final da linha. Penso que, juntamente com o OP, geramos permutações mais significativas de erros de digitação que isso pode ter.
eerorika

21

O motivo da saída inesperada é um erro de digitação. Você provavelmente quis dizer

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

Se ignorarmos as strings que têm a saída esperada, ficaremos com:

cout << cout;

Desde C ++ 11, isso é mal formado. std::coutnão é implicitamente conversível em algo que std::basic_ostream<char>::operator<<(ou uma sobrecarga de não-membro) aceitaria. Portanto, um compilador em conformidade com os padrões deve avisá-lo pelo menos para fazer isso. Meu compilador se recusou a compilar seu programa.

std::coutseria conversível boole a sobrecarga booleana do operador de entrada de fluxo teria a saída observada de 1. No entanto, essa sobrecarga é explícita, portanto, não deve permitir uma conversão implícita. Parece que sua implementação de compilador / biblioteca padrão não está estritamente em conformidade com o padrão.

Em um padrão anterior ao C ++ 11, isso é bem formado. Naquela época, std::couthavia um operador de conversão implícito no void*qual havia uma sobrecarga de operador de entrada de fluxo. A saída para isso, no entanto, seria diferente. imprimiria o endereço de memória do std::coutobjeto.


11

O código publicado não deve ser compilado para nenhum C ++ 11 (ou compilador compatível posterior), mas deve ser compilado sem sequer um aviso nas implementações anteriores ao C ++ 11.

A diferença é que o C ++ 11 tornou explícita a conversão de um fluxo em um booleano:

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

Alteração: especifique o uso de explícito nos operadores de conversão booleanos existentes.
Justificativa: esclarecer intenções, evitar soluções alternativas.
Efeito no recurso original: o código C ++ 2003 válido que depende de conversões booleanas implícitas não será compilado com este Padrão Internacional. Essas conversões ocorrem nas seguintes condições:

  • passando um valor para uma função que recebe um argumento do tipo bool;
    ...

operador ostream << é definido com um parâmetro bool. Como uma conversão para bool existia (e não era explícita) é anterior ao C ++ 11, cout << coutfoi traduzida para a cout << truequal produz 1.

E de acordo com C.2.15, isso não deve mais ser compilado a partir do C ++ 11.


3
Nenhuma conversão boolexiste no C ++ 03, no entanto, existe std::basic_ios::operator void*()uma significativa como a expressão de controle de um condicional ou loop.
Ben Voigt

7

Você pode facilmente depurar seu código dessa maneira. Quando você usa coutsua saída é armazenada em buffer para que você possa analisá-la da seguinte maneira:

Imagine a primeira ocorrência de coutrepresenta o buffer e o operador <<representa anexando ao final do buffer. O resultado do operador <<é o fluxo de saída, no seu caso cout. Você começa em:

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

Depois de aplicar as regras acima, você obtém um conjunto de ações como este:

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

Como eu disse antes, o resultado buffer.append()é buffer. No início, seu buffer está vazio e você tem a seguinte instrução para processar:

declaração: buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);

amortecedor: empty

Primeiro você tem o buffer.append("2+3 = ")que coloca a string fornecida diretamente no buffer e se torna buffer. Agora seu estado se parece com isso:

declaração: buffer.append(cout).append(2 + 3).append(endl);

amortecedor: 2+3 = 

Depois disso, você continua analisando sua declaração e se depara coutcom um argumento a ser anexado ao final do buffer. O couté tratado 1assim, você será anexado 1ao final do seu buffer. Agora você está neste estado:

declaração: buffer.append(2 + 3).append(endl);

amortecedor: 2+3 = 1

A próxima coisa que você tem no buffer é que, 2 + 3como a adição tem maior precedência do que o operador de saída, você primeiro adicionará esses dois números e depois colocará o resultado no buffer. Depois disso você obtém:

declaração: buffer.append(endl);

amortecedor: 2+3 = 15

Finalmente, você adiciona valor endlao final do buffer e possui:

declaração:

amortecedor: 2+3 = 15\n

Após esse processo, os caracteres do buffer são impressos no buffer para a saída padrão, um por um. Portanto, o resultado do seu código é 2+3 = 15. Se você olhar para isso, você obter adicional 1de coutque você tentou imprimir. Ao remover << coutda sua declaração, você obterá a saída desejada.


6
Embora tudo isso seja verdade (e lindamente formatado), acho que está implorando a pergunta. Eu acredito que a pergunta se resume a "Por que cout << coutproduzir 1em primeiro lugar?" , e você acabou de afirmar que isso ocorre no meio de uma discussão sobre o encadeamento do operador de inserção.
Inútil

1
+1 para a bela formatação. Considerando que esta é sua primeira resposta, é bom que você está tentando ajuda :)
gldraphael
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.