Почему cout печатает "2 + 3 = 15" в этом фрагменте кода?

Почему вывод программы ниже, что это такое?

#include <iostream>
using namespace std;

int main(){

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

производит

2+3 = 15

вместо ожидаемого

2+3 = 5

Этот вопрос уже прошел несколько циклов закрытия/повторного открытия.

Перед тем, как начать голосование, рассмотрите эту мета-дискуссию об этой проблеме.

Ответ 1

Умышленно или случайно вы имеете << в конце первой выходной строки, где вы, вероятно, имели в виду ;. Таким образом, у вас есть

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

Итак, вопрос сводится к следующему: почему cout << cout; печатать "1"?

Это оказывается, возможно, удивительно, тонким. std::cout через свой базовый класс std::basic_ios предоставляет оператор преобразования определенного типа, который предназначен для использования в булевом контексте, как в

while (cout) { PrintSomething(cout); }

Это довольно плохой пример, так как трудно получить вывод для отказа, но std::basic_ios на самом деле является базовым классом как для входных, так и для выходных потоков, а для ввода он имеет гораздо больше смысла:

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

(выходит из цикла в конце потока или когда символы потока не образуют действительное целое число).

Теперь точное определение этого оператора преобразования изменилось между версиями стандарта С++ 03 и С++ 11. В более старых версиях он был operator void*() const; (обычно реализован как return fail() ? NULL : this;), а в новее он explicit operator bool() const; (обычно реализованный просто как return !fail();). Оба объявления отлично работают в булевом контексте, но ведут себя по-другому, когда (неверно) используется вне такого контекста.

В частности, в соответствии с правилами С++ 03 cout << cout будет интерпретироваться как cout << cout.operator void*() и напечатать некоторый адрес. В соответствии с правилами С++ 11 cout << cout не следует компилировать вообще, поскольку оператор объявлен explicit и, следовательно, не может участвовать в неявных преобразованиях. На самом деле это была основная мотивация изменений - предотвращение компрометации бессмысленного кода. Компилятор, который соответствует стандарту, не будет создавать программу, которая печатает "1".

По-видимому, некоторые реализации С++ позволяют смешивать и сопоставлять компилятор и библиотеку таким образом, что создает несоответствующий результат (цитируя @StephanLechner: "Я нашел параметр в xcode, который производит 1, а другой параметр, который дает адрес: Языковой диалект С++ 98 в сочетании с" стандартной библиотекой libС++ (стандартная библиотека LLVM с поддержкой С++ 11) "дает 1, тогда как С++ 98 в сочетании с libstdc (стандартная библиотека gnu С++) дает адрес;" ). У вас может быть компилятор С++ 03, который не понимает explicit операторов преобразования (которые являются новыми в С++ 11) в сочетании с библиотекой в ​​стиле С++ 11, которая определяет преобразование как operator bool(). При таком смешивании становится возможным, чтобы cout << cout интерпретировался как cout << cout.operator bool(), который, в свою очередь, просто cout << true и печатает "1".

Ответ 2

Как говорит Игорь, вы получаете это с помощью библиотеки С++ 11, где std::basic_ios имеет operator bool вместо operator void*, но каким-то образом не объявляется (или не рассматривается как) explicit. См. здесь для правильного объявления.

Например, соответствующий компилятор С++ 11 даст тот же результат с

#include <iostream>
using namespace std;

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

но в вашем случае static_cast<bool> допускается (ошибочно) как неявное преобразование.


Изменить: Поскольку это не обычное или ожидаемое поведение, может быть полезно знать вашу платформу, версию компилятора и т.д.


Изменить 2: Для справки код обычно записывается либо как

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

или

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

и он смешивает два стиля вместе, что выявило ошибку.

Ответ 3

Причиной неожиданного вывода является опечатка. Вероятно, вы имели в виду

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

Если мы проигнорируем строки, которые имеют ожидаемый результат, мы остаемся с:

cout << cout;

Так как С++ 11, это плохо сформировано. std::cout не является неявным образом конвертируемым ко всему, что примет std::basic_ostream<char>::operator<< (или не-членная перегрузка). Поэтому компилятор, соответствующий стандартам, должен хотя бы предупредить вас об этом. Мой компилятор отказался компилировать вашу программу.

std::cout будет конвертируемым в bool, а перегрузка bool оператора ввода потока будет иметь наблюдаемый вывод 1. Однако эта перегрузка является явной, поэтому она не должна допускать неявное преобразование. Похоже, что реализация вашего компилятора/стандартной библиотеки строго не соответствует стандарту.

В стандарте pre-С++ 11 это хорошо сформировано. Тогда std::cout имел оператор неявного преобразования в void*, который имеет перегрузку оператора ввода потока. Выход для этого, однако, будет отличаться. он будет печатать адрес памяти объекта std::cout.

Ответ 4

Опубликованный код не должен компилироваться для любого С++ 11 (или более позднего совместимого компилятора), но он должен компилироваться без предупреждения даже в версиях pre С++ 11.

Различие заключается в том, что С++ 11 сделал преобразование потока в явное выражение bool:

C.2.15 Статья 27: Библиотека ввода/вывода [diff.cpp03.input.output] 27.7.2.1.3, 27.7.3.4, 27.5.5.4

Изменить: указать использование явного в существующих операторах логического преобразования
Обоснование: Уточнить намерения, избежать обходных путей.
Влияние на оригинальную функцию: действительный код С++ 2003, который полагается на неявные логические преобразования, не сможет с этим международным стандартом. Такие преобразования происходят в следующих условиях:

  • передача значения функции, которая принимает аргумент типа bool;
    ...
Оператор

ostream < определяется параметром bool. Поскольку преобразование в bool существовало (и не было явным), это pre-С++ 11, cout << cout было переведено на cout << true, что дает 1.

И согласно C.2.15, это не должно больше компилироваться, начиная с С++ 11.

Ответ 5

Вы можете легко отладить свой код таким образом. Когда вы используете cout, ваш вывод буферизируется, поэтому вы можете анализировать его следующим образом:

Представьте, что первое появление cout представляет буфер, а оператор << представляет собой добавление к концу буфера. Результатом оператора << является выходной поток, в вашем случае cout. Вы начинаете с:

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

После применения вышеуказанных правил вы получите набор действий, подобных этому:

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

Как я уже говорил, результатом buffer.append() является буфер. В начале ваш буфер пуст, и вы должны выполнить следующее выражение:

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

buffer: empty

Сначала у вас есть buffer.append("2+3 = "), который помещает данную строку непосредственно в буфер и становится buffer. Теперь ваше состояние выглядит следующим образом:

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

buffer: 2 + 3   =  

После этого вы продолжаете анализировать свое утверждение, и вы сталкиваетесь с cout как аргумент для добавления в конец буфера. cout рассматривается как 1, поэтому вы добавите 1 в конец вашего буфера. Теперь вы находитесь в этом состоянии:

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

buffer: 2 + 3   =   1

Следующее, что у вас есть в буфере, - 2 + 3, и поскольку добавление имеет более высокий приоритет, чем оператор вывода, вы сначала добавите эти два числа, а затем вы поместите результат в буфер. После этого вы получите:

: buffer.append(endl);

buffer: 2 + 3   =   1 5

Наконец, вы добавляете значение endl в конец буфера, и у вас есть:

утверждение:

buffer: 2 + 3   =   1 5 \n

После этого процесса символы из буфера печатаются из буфера в стандартный вывод один за другим. Таким образом, результат вашего кода 2+3 = 15. Если вы посмотрите на это, вы получите дополнительные 1 из cout, которые вы пытались распечатать. Удалив << cout из вашего заявления, вы получите желаемый результат.