Здравствуйте Андрей Тарасевич
Все совершенно верно про неопределенное поведение.
Любая функция с побочным эффектом (а оператор автоинкремента таковым является) способна дать такой же результат.
Тут я спорить не буду.
(И вообще такие фокусы делать нехорошо, дорогие любители cin/cout).
Просто логика работы компилятора была, что называется, налицо.
И именно об этом я и написал.
Строится дерево выражения
a.f(z1, 1).f(z2, 2).f(z3, 3)
A::f(A::f(A::f(a, z1, 1), z2, 2), z3, 3)
- это была прямая польская запись
обратная польская запись:
stdcall
3 z3 2 z2 1 z1 a A::f A::f A::f
pascal
a z1 1 A::f z2 2 A::f z3 3 A::f
В дебаге компилятор транслирует выражение наиболее халявным способом.
А это — вывалить все на стек и не использовать регистры или временные переменные. Именно здесь конвенция и играет роль.
С оптимизацией он (может быть) догадается завести временную переменную, а то и вообще обойдется регистрами.
Кстати, только запятая и логические операторы ( && и || ) имеют жесткий порядок вычисления операндов (слева направо).
А если их перегрузить — будет ли компилятор придерживаться этого правила?
z = 10;
(f(++z, 1), true) && (f(++z, 2), true);
// получится f(11,1), f(12,2) ?
class A
{
public:
const A& operator && (int z) const { g(z); return this; }
};
A a;
z = 10;
a && (++z) && (++z);
// получится теперь g(11), g(12) ?