Когда оператор << ссылается на оператор ввода и когда на побитовый сдвиг влево?
Когда operator <<
ссылается на оператор вставки и когда он ссылается на побитовый сдвиг влево?
Это приведет к выходу 10
, а operator <<
относится к сдвигу влево.
cout << a.b() << a.a.b << endl;
И это выведет 11
, operator <<
относится к оператору вставки.
cout << a.b();
cout << a.a.b ;
Я запутался, когда operator <<
(при использовании с cout
) относится к оператору сдвига влево?
#include <iostream>
using namespace std;
class A {
public:
A() { a.a = a.b = 1; }
struct { int a, b; } a;
int b();
};
int A::b(){
int x=a.a;
a.a=a.b;
a.b=x;
return x;
};
int main(){
A a;
a.a.a = 0;
a.b();
cout << a.b() << a.a.b << endl; // ?????
return 0;
}
Ответы
Ответ 1
Проблема, с которой вы сталкиваетесь, не связана с < оператор. В каждом случае вызывается оператор вставки.
Однако вы столкнулись с проблемой, связанной с порядком оценки в командной строке
cout << a.b() << a.a.b << endl;
Функция a.b()
имеет побочный эффект. Он меняет значения a.a.a и a.a.b. Таким образом, очевидно, что a.b() вызывается до или после оценки значения ov a.a.b
.
В С++ порядок оценки не указан, см. cppreference.com для более подробного обсуждения.
Ответ 2
Это будет выводить 10, а оператор < см. сдвиг влево.
cout < a.b() < a.a.b < епсИ;
Это связано с тем, что порядок оценки операндов не определен. С clang он выводит 11, но с gcc он выдает 10.
Ваш код:
cout << a.b() << a.a.b << endl;
можно заменить на:
std::cout.operator<<(a.b()).operator<<(a.a.b);
clang сначала оценивает a.b()
, затем a.a.b
, g++ делает это наоборот. Поскольку ваш a.b()
изменяет переменные, вы получаете разные результаты.
Когда вы переписываете свой код как:
cout << a.b();
cout << a.a.b ;
тогда у вас есть два оператора выражения полного выражения, здесь нет неопределенного поведения, связанного с оценкой операнда. Таким образом, вы получаете тот же результат.
Ответ 3
В вашем случае все operator <<
являются операторами ввода потока вывода, поскольку их левый аргумент имеет тип ostream&
, и они группируются слева направо.
Разница в выходе вызвана порядком оценки аргументов функции:
cout << a.b() << a.a.b
является
operator<<(operator<<(cout, a.b()), a.a.b)
поэтому выход зависит от того, какой из параметров a.a.b
или a.b()
оценивается первым. Это фактически не определено текущим стандартом (С++ 14), чтобы вы могли получить 11
.
AFAIK в С++ 17 11
будет единственным допустимым выходом для обоих случаев, поскольку он обеспечивает оценку параметров функции слева направо.
Обновление: это, похоже, неверно, поскольку комитет решил (с N4606) перейти с неопределенным секвенированием оценка параметров, упомянутая внизу P0145R2. См. [Expr.call]/5.
Update2: Поскольку мы говорим о перегруженных операторах здесь, применяется [over.match.oper]/2 в N4606, в котором говорится
Однако операнды упорядочены в порядке, предписанном для встроенного оператора.
Таким образом, порядок оценки будет хорошо определен в С++ 17. Это недоразумение, по-видимому, было предсказано авторами P0145:
Мы не считаем, что такой недетерминированность приносит существенную добавленную выгоду оптимизации, но она увековечивает путаницу и опасности вокруг порядка оценок в вызовах функций
Ответ 4
Этот вызов:
cout << a.b() << a.a.b << endl;
Сначала рассмотрим :
cout << a.b()
который соответствует оператору вставки и возвращает подтверждение для cout. Таким образом, инструкция будет выглядеть следующим образом:
(returned reference to cout) << a.a.b
который снова вызовет оператор вставки и т.д.
Если ваша инструкция:
cout << (a.b() << a.a.b) << endl;
часть между скобками будет считаться первой:
a.b() << a.a.b
на этот раз у вас есть оператор между 2 int
: компилятор может разрешить его только как вызов побитового оператора.
Ответ 5
Двоичные операторы, такие как <<
, имеют два свойства, которые определяют их использование: (оператор) приоритет и (левая или правая) ассоциативность. В этом случае ассоциативность является ключом, и, см., Например, http://en.cppreference.com/w/c/language/operator_precedence, оператор <<
имеет ассоциацию слева направо, поэтому они упорядочены (как бы скобки) слева направо:
((cout << a.b()) << a.a.b) << endl;
или в словах, обозначенных как cout << a.b()
, затем << a.a.b
, а затем << endl
.
После этого секвенирования перегрузка оператора вступает в силу при каждом вызове <<
с заданными типами, который затем определяет, какая перегрузка вызывается, и, следовательно, если это a cout
-операция или сдвиг.
Ответ 6
Без скобок операнды с обеих сторон <<
определяют значение: int << int == shift
, stream << any == insertion
.
Это "повторное использование" оператора может быть запутанным, indead. Но вы можете решить двусмысленности с помощью круглых скобок: stream << (int << int) == "int"