Ответ 1
std::cout
является экземпляром std::ostream
. std::cout << "something"
вызывает одну из перегрузок operator<<
, как это было бы сделано для любого экземпляра std::ostream
.
Он "особый" в том, что он ссылается на консоль, но в противном случае он ведет себя точно так же, как ofstream
или ostringstream
.
EDIT: цепочка работает так же, как и для любых других операторов. Рассмотрим:
class MyType
{
friend std::ostream& operator<<(std::ostream& target, const MyType& source);
int val;
public:
MyType()
: val(0)
{ }
MyType& Add(int toAdd)
{
val += toAdd;
return *this;
}
};
MyType& operator+(MyType& target, int toAdd)
{
return target.Add(toAdd);
}
std::ostream& operator<<(std::ostream& target, const MyType& source)
{
target << source.val;
return target; //Make chaining work
}
int main()
{
MyType value1;
value1 + 2 + 3 + 4;
std::cout << value1 << " and done!" << std::endl;
}
В этом случае цепочка для + s на MyType
работает по той же причине, что <<
работает поверх std::ostream
. Оба +
и <<
являются лево-ассоциативными, что означает, что они оцениваются слева направо. В случае перегруженных операторов оператор заменяется эквивалентным вызовом функции.
EDIT2: немного подробнее:
Скажем, вы компилятор, и вы разбираете
std::cout << value1 << " and done!" << std::endl;
Во-первых, <<
остается ассоциативным, поэтому вы начинаете с левой стороны. Вы оцениваете первый <<
и включаете его в вызов функции:
operator<<(std::cout, value1) << " and done!" << std::endl;
Затем вы увидите, что у вас снова есть std::ostream
(результат вызова operator<<
) и char *
, который вы снова включаете в вызов функции:
operator<<(operator<<(std::cout, value1)," and done!") << std::endl;
et cetera, пока вы не обработали весь оператор.