Этот вопрос я встретил на одном форуме, на котором там же и ответил. Но я решил продублировать здесь этот вопрос и дать более полный ответ на него, так как это познавательно.
Итак, что будет выведено на консоль в результате выполнения предложения
std::cout << std::cout;
На первый взгляд такая запись вообще выглядет бессмысленной, и кто-то даже может предположить, что возникнет ошибка компиляции. Каково же будет его удивление, когда он обнаружит, что код компилируется!
Мы настолько привыкли использовать перегруженный оператор
<< для вывода на консоль фундаментальных типов или определеннных пользователем типов данных, что предполагаем, что в правой части этого выражения могут присутствовать только такие объекты указанных типов. Например,
int x = 5;
char c = 'A";
char a[] = "An array.";
std::cttring s( "A string." );
std::cout << x << ", " << c << ", " << a << ", " << s << std::endl;
И даже сами часто определяем свои оператор-функции перегрузки оператора
<< для собственных типов. Например,
#include <iostream>
struct Point
{
Point() : x( 0 ), y( 0 ) {}
Point( int x, int y ) : x( x ), y( y ) {}
int x;
int y;
};
std::ostream & operator <<( std::ostream &os, const Point &rhs )
{
return ( os << "< " << rhs.x << ", " << rhs.y << " >" );
}
int main()
{
Point p( 10, 20 );
std::cout << p << std::endl;
}
Но какой смысл имеет ниже приведенный оператор?!
std::ostream & operator <<( std::ostream &os, const std::ostream &rhs ); Очевидно, что такого объявления в стандартной библиотеки С++ нет. Тогда что происходит, когда мы пишем
std::cout << std:;cout, почему компилируется код ?
Когда компилятор встречает такое выражение, то он начинает искать подходящую оператор-функцию. Так как в глобальном пространстве имен такой функции нет, то компилятор осуществляет поиска в тех пространствах имен, к которым принадлежат аргументы. В данном случае таким пространством имен будет стандартное пространство имен
std:: .
В этом пространстве имен определено много таких операторов-функций для перегруженного оператора
<< . Но ни один из них не имеет в качестве второго параметра параметр с типом
std::ostream. Тогда компилятор ищет функции преобразования, которые позволят преобразовать объект
std::ostream в объект, для типа которого определен перегруженный оператор
<<. И такая функция находится! В классе
std::basic_ios, который является базовым для класса
std::ostream есть оператор-функция преобразования типа в
void *. Мы часто пользуемся этой функцией, даже не задумываясь, что именно она используется, например, в конструкциях, подобных следующей:
std::vector<int> v;
int x;
while ( std::cin >> x ) v.push_back( x );
Здесь правда вместо
std::ostream используется объект
std::cin, принадлежащий типу
std::istream, но суть не меняется, так как для обоих типов определена данная оператор-функция преобразования в
void *.
А для типа
void * в классе
std::ostream определена оператор-функция для оператора
<<.
Итак, когда имеется предложение вида
std::cout << std::cout;, то компилятор неявно преобразует правый объект
std::cout в объект типа
void *, а далее использует оператор-функцию
<< для вывода объекта
void *.
Казалось бы на этом можно поставить точку. Но действительно ли мы можем сказать, что выведется на консоль? Увы, мы не можем это сказать, даже зная то, какие функции вызываются! Дело в том, что стандарт не оговаривает, какое значение должна возвращать оператор-функция преобразования в тип
void *. Например, компилятор Borland C++ Builder 5.0 в случае отсутствия ошибки в потоке возвращает значение
( void * ) 1. В то время как, например, компилятор MS VC++ 2010 возвращает
( void * ) this.
Но я вас удивлю еще больше, когда скажу. что может быть вообще ничего не выведено на консоль! Дело в том, что значение, возвращаемое оператор-функцией
void *, зависит от состояния потока! Если при работе потока произошли ошибки, или, например, был достигнут конец потока (состояние
eof), то эта оператор-функция преобразования в тип
void * возвращает
null.
Вот как эта оператор-функция выглядет в MS VC++ 2010
operator void *() const
{
return ( fail() ? 0 : ( void * )this );
}
А перед выводом в поток, всегда проверяется состояние потока. И если поток содержит ошибки, то вывод игнорируется.
Поэтому в общем случае вы не только не можете сказать, какое значение будет выведено на консоль при выполнении предложения
std::cout << std::cout;, но даже не сможете сказать, а будет ли вообще что-то выведено!
В качестве подтверждения сказанного можете запустить следующий тестовый пример
#include <iostream>
int main()
{
std::cout << std::cout; // вывод результата преобразования std:;cout в void *
std::cout.setstate( std::ios_base::badbit );
std::cout << std::cout; // отсутствия вывода
std::cout.clear();
}