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.