Не допускайте зависимости от порядка вычисления из-за побочных эффектов
Выражение должно содержать не более одного вызова с побочным эффектом. Если побочных эффектов несколько - выносите каждый в отдельный оператор (statement) с сохранением результата в именованную переменную.
Неправильно:
Этот пример до сих пор обожают показывать на собеседованиях, с вопросом: "чему равен x?".
Мы имеем дело с классическим неопределенным поведением (undefined behavior). Стандарт не гарантирует, что будет вычислено в первую очередь, что во вторую (i++ или ++i).
C++17 теперь требует, чтобы правая часть (++i) оператора + вычислялась первой, а левая (i++) - второй. Но поскольку в одном выражении есть два вычисления с побочным эффектом - все равно UB.
Правильно:
Золотое правило: Одно выражение - один побочный эффект.
Помещайте результат вызова функции с побочным эффектом в именованную переменную до передачи в функцию.
Неправильно:
int counter = 0;
void foo(int, int);
int baz() { return ++counter; }
int bar() { return counter++; }
foo(baz(), bar()); // порядок вызова неопределен
В C++ порядок вычисления аргументов функции не определён (unspecified behavior, не путать с undefined behavior). Это значит, что компилятор может вычислить baz() и bar() в любом порядке - хоть слева направо, хоть справа налево, хоть вперемешку. Порядок может меняться между разными компиляторами, версиями одного компилятора или даже разными местами вызова в одной программе.
Поэтому если baz() и bar() имеют побочные эффекты, зависящие от порядка выполнения, необходимо их результаты помещать в отдельные переменные.
Правильно:
int counter = 0;
void foo(int, int);
int baz() { return ++counter; }
int bar() { return counter++; }
int a = baz();
int b = bar();
foo(a, b); // гарантируется foo(1, 1);
Важно! Начиная с C++17 гарантируется, что для операторов << и >> в потоках (перегруженных как операторы сдвига) выражения вычисляются слева направо. Но для обычных вызовов функций правило осталось прежним.
Пример: