for (int i = 0, j = 10; i < j; ++i, --j)
{
const int l = a[i] + 1;
const int r = a[j] + 1;
const std::string msg = std::to_string(l + r) + " bottles of rum";
std::cout << msg << std::endl;
}
1) i, j обьявлены в заголовке цикла. Они создаются 1 раз и живут до конца цикла.
2) l, r обьявлены в теле цикла. Каждую итерацию мы присваиваем им нове значение. Поскольку int это базовый тип, переменная просто записывется на стек без дополнительного выделения памяти.
3) msg это уже переменная более сложного типа. Хотя она и создается на стеке, внутри конструктора она может выделять память с помощью new. И это будет происходить на каждой итерации.
4) Более того, std::to_string(l + r) создаст временный объект std::string, который удалится после завершения operator+
С одной стороны полезно понимать какие операции могут приводить к выделению памяти, так как это не быстро. С другой об этом можно не думать слишком много при написании бизнес кода. Преждевременная оптимизация это не есть хорошо.
Оптимизации компилятора работают достаточно хорошо. Но они не всесильны, и не должны менять поведение. Поэтому, содавать тяжелый обьект в цикле, если можно сделать это один раз, не очень хорошая идея.