Не допускайте зависимости от порядка вычисления из-за побочных эффектов

Выражение должно содержать не более одного вызова с побочным эффектом. Если побочных эффектов несколько - выносите каждый в отдельный оператор (statement) с сохранением результата в именованную переменную.

Неправильно:

int i = 0;
int x = i+++++i;

Этот пример до сих пор обожают показывать на собеседованиях, с вопросом: "чему равен x?".

Мы имеем дело с классическим неопределенным поведением (undefined behavior). Стандарт не гарантирует, что будет вычислено в первую очередь, что во вторую (i++ или ++i).

C++17 теперь требует, чтобы правая часть (++i) оператора + вычислялась первой, а левая (i++) - второй. Но поскольку в одном выражении есть два вычисления с побочным эффектом - все равно UB.

Правильно:

int i = 0;
int x = i++;
x += ++i;
// или наоборот, в зависимости, что вы хотите выполнить первым

Золотое правило: Одно выражение - один побочный эффект.

Помещайте результат вызова функции с побочным эффектом в именованную переменную до передачи в функцию.

Неправильно:

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 гарантируется, что для операторов << и >> в потоках (перегруженных как операторы сдвига) выражения вычисляются слева направо. Но для обычных вызовов функций правило осталось прежним.

Пример:

#include <iostream>

int counter = 0;

int next() {
    return ++counter;
}

int main() {
    // До C++17: порядок вычисления next() не гарантирован,
    //           могло вывести "2 1" или "1 2"
    // C++17: гарантированно выведет "1 2"
    std::cout << next() << " " << next();
    //            ↑ 1            ↑ 2
    //          сначала        потом
}