std::cout
является экземпляром std::ostream
.std::cout << "something"
вызывает одну из operator<<
перегрузок, как это было бы сделано для любого экземпляра std::ostream
.
Это «особенный» в том смысле, что он ссылается на консоль, но в остальном он ведет себя точно так же, как ofstream
или ostringstream
.
РЕДАКТИРОВАТЬ : Цепочка работает так же, как и для любых других операторов.Рассмотрим:
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
работает по той же причине, что и <<
s на вершине 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;
и так далее, пока не обработаете весь оператор.