цепочка ostream, порядок вывода

У меня есть функция, которая принимает ссылку ostream в качестве аргумента, записывает некоторые данные в поток, а затем возвращает ссылку на тот же поток, например:

#include <iostream>

std::ostream& print( std::ostream& os ) {
  os << " How are you?" << std::endl;
  return os;
}

int main() {
  std::cout << "Hello, world!" << print( std::cout ) << std::endl;
}

Вывод этого кода:

 How are you?
Hello, world!0x601288

Однако, если я разделю выражения цепочки на два оператора, как это

int main() {
  std::cout << "Hello, world!";
  std::cout << print( std::cout ) << std::endl;
}

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

Hello, world! How are you?
0x600ec8

Я хотел бы понять, что здесь происходит. Имеет ли нормальная функция приоритет над operator<<, и поэтому порядок вывода меняется на противоположный? Как правильно написать функцию, которая вставляет данные в ostream, но также может связываться с operator<<?


person LowTechGeek    schedule 19.01.2012    source источник


Ответы (5)


Поведение вашего кода не указано в соответствии со стандартом C++.

Объяснение

Следующее (я удалил std::endl для простоты)

std::cout << "Hello, world!" << print( std::cout ); 

эквивалентно этому:

operator<<(operator<<(std::cout, "Hello, World!"), print(std::cout));

который является вызовом функции, передавая два аргумента:

  • Первый аргумент: operator<<(std::cout, "Hello, World!")
  • Второй аргумент: print(std::cout)

Теперь Стандарт не определяет порядок, в котором оцениваются аргументы. Оно не указано. Но ваш компилятор, по-видимому, сначала оценивает второй аргумент, поэтому он сначала печатает How are you?, оценивая второй аргумент как значение типа std::ostream&, которое затем передается вызову, показанному выше (который значением является сам объект std::cout).

Почему шестнадцатеричный вывод?

Вы получаете шестнадцатеричный вывод, потому что второй аргумент имеет значение std::cout, которое печатается как шестнадцатеричное число, потому что std::cout неявно преобразуется в значение указателя типа void*, поэтому оно печатается как шестнадцатеричное число.

Попробуй это:

void const *pointer = std::cout; //implicitly converts into pointer type!
std::cout << std::cout << std::endl;
std::cout << pointer << std::endl;

Он будет печатать одно и то же значение для обоих. Например, этот пример на ideone выводит следующее:

0x804a044
0x804a044 

Также обратите внимание, что я не использовал явное приведение; скорее std::cout неявно преобразуется в тип указателя.

Надеюсь, это поможет.


Как правильно написать функцию, которая вставляет данные в ostream, но также может связываться с operator<<?

Когда это зависит от того, что вы подразумеваете под цепочкой? Очевидно, что следующее не будет работать (как объяснено выше):

std::cout << X << print(std::cout) << Y << Z; //unspecified behaviour!

Как бы вы не написали print().

Однако это четко определено:

print(std::cout) << X << Y << Z; //well-defined behaviour!
person Nawaz    schedule 19.01.2012
comment
Не отвечает Как правильно написать функцию, которая вставляет данные в ostream, но также может связываться с operator<<? - person Ben Voigt; 19.01.2012
comment
@Mr.Anubis: между делом я обедал :P - person Nawaz; 19.01.2012

Причина в том, что ваша функция print() будет оцениваться до остальной части оператора и возвращать ссылку на cout, которая затем фактически печатается как указатель (cout ‹‹ cout). Этот порядок оценки на самом деле является неуказанным поведением, но, похоже, это относится к вашему компилятору.

Что касается определения «функции» с поддержкой потока, которая фактически имеет определенное поведение с той же функциональностью, это будет работать;

#include <iostream>

template <class charT, class traits>
  std::basic_ostream<charT,traits>& print ( std::basic_ostream<charT,traits>& os )
{
        os << " How are you?" << std::endl;
        return os;
}

int main() {
  std::cout << "Hello, world!" << print << std::endl;
}

См. также этот ответ для более подробной информации о том, что на самом деле означает "не указано" в этом случае.

person Joachim Isaksson    schedule 19.01.2012

В вашем заявлении std::cout << "Hello, world!" << print( std::cout ) << std::endl не определено, происходит ли std::cout << "Hello, world!" до или после print( std::cout ). Вот почему заказ может быть не таким, как вы ожидаете.

Шестнадцатеричное значение исходит из того факта, что вы также выполняете std::cout << std::cout (print возвращает std::cout, которое передается в цепочку <<). Правая рука std::cout преобразуется в void * и печатается на выходе.

person bames53    schedule 19.01.2012

Это сработает, чтобы объединить print с << и контролировать порядок:

print( std::cout << "Hello, world!" ) << std::endl;

Или, если вам нужна функция, вызываемая с помощью <<, см. ответ Иоахима.

person Ben Voigt    schedule 19.01.2012

Шестнадцатеричный вывод

До C++11 класс std::ostream имел функцию преобразования в void*. Поскольку ваша функция print возвращает std::ostream&, при вычислении std::cout << print(...) возвращаемое значение std::ostream lvalue будет неявно преобразовано в void*, а затем выведено как значение указателя. Вот почему существует шестнадцатеричный вывод.

Начиная с C++11, эта функция преобразования заменяется явной функцией преобразования в bool, поэтому попытка вывести объект std::ostream становится неправильной.

Порядок оценки

До C++17 перегруженный оператор считался вызовом функции для анализа порядка вычисления, а порядок вычисления различных аргументов вызова функции не определялся. Поэтому неудивительно, что функция print вычисляется первой, что приводит к тому, что сначала выводится How are you?.

Начиная с C++17, порядок вычисления операндов оператора << строго слева направо, а операнды перегруженного оператора имеют тот же порядок вычисления, что и встроенный (подробнее см. здесь). Таким образом, ваша программа всегда будет получать вывод (предположим, что print возвращает что-то, что можно вывести)

Hello, world! How are you?
something returned by print

РЕАЛЬНЫЙ ПРИМЕР

person xskxzr    schedule 05.03.2018