ядро языка · синтаксисcore language · syntax
Цикл по всем элементам контейнера или массива без явного итератора и условия выхода. Синтаксический сахар, который компилятор целиком разворачивает в обычный for с begin()/end().A loop over every element of a container or array without an explicit iterator or exit condition. Syntactic sugar that the compiler fully expands into an ordinary for loop with begin()/end().
for (декларация : выражение-диапазона) тело — форма цикла, которая перебирает все элементы диапазона, для которого определены begin()/end() (как свободные функции или методы), от первого до последнего, без ручного объявления итератора.for (declaration : range-expression) body — a loop form that iterates over every element of a range for which begin()/end() are defined (as free functions or member functions), from first to last, without manually declaring an iterator.
for (std::vector<int>::iterator it = v.begin();
it != v.end(); ++it) {
std::cout << *it;
}
for (int x : v) {
std::cout << x;
}
Итератор вручную — это шаблон кода, который почти всегда одинаков и почти всегда лишний:A hand-written iterator is boilerplate that is almost always identical and almost always unnecessary:
Опечатка вроде it != v.end() - 1 или сравнение итераторов разных контейнеров — классические источники багов ручного цикла. Range-based for устраняет саму возможность ошибиться в условии выхода.A typo like it != v.end() - 1, or comparing iterators from different containers, are classic sources of bugs in a hand-written loop. Range-based for eliminates the very possibility of getting the exit condition wrong.
Массивы, контейнеры STL, инициализатор-листы, пользовательские типы с begin()/end() — все перебираются одной и той же конструкцией, без знания конкретного типа итератора.Arrays, STL containers, initializer lists, custom types with begin()/end() — all of them are iterated with the same construct, without knowing the concrete iterator type.
К 2011 году for-each-цикл уже был стандартом де-факто в Java, Python, C# — C++ отставал синтаксически, хотя обладал всей инфраструктурой (итераторы) для реализации того же самого.By 2011, a for-each loop was already the de facto standard in Java, Python, and C# — C++ was lagging syntactically, despite already having all the infrastructure (iterators) needed to implement the very same thing.
Компилятор разворачивает range-based for в обычный for с временными переменными __begin/__end — ровно то же самое, что писалось бы руками:The compiler expands range-based for into an ordinary for loop with temporary variables __begin/__end — exactly what you would write by hand:
for (auto& x : container) { body }
// эквивалент, который генерирует компилятор:// the equivalent the compiler generates:
{
auto&& __range = container;
auto __begin = begin(__range); // ADL: begin(__range) или __range.begin()// ADL: begin(__range) or __range.begin()
auto __end = end(__range); // приоритет — member begin()/end(),// priority goes to member begin()/end(),
// иначе свободная функция через ADL// otherwise a free function found via ADL
for (; __begin != __end; ++__begin) {
auto& x = *__begin;
body
}
}
Для массива фиксированного размера begin/end — это просто container и container + N, известные на этапе компиляции. Для контейнера с пользовательским итератором — ровно то же, что и в ручном цикле: никакого дополнительного рантайм-кода range-based for не добавляет, оптимизатор видит идентичный IR.For a fixed-size array, begin/end are just container and container + N, known at compile time. For a container with a custom iterator, it's exactly the same as a hand-written loop — range-based for adds no additional runtime code, and the optimizer sees identical IR.
Без & декларация auto x копирует каждый элемент диапазона. Для тяжёлых типов (строки, векторы, кастомные объекты) это незаметный, но реальный overhead — почти всегда нужен const auto& для чтения или auto& для изменения.Without &, the declaration auto x copies every element of the range. For heavy types (strings, vectors, custom objects) this is an invisible but very real overhead — you almost always want const auto& for reading or auto& for mutating.
for (auto x : getVector()) — временный результат getVector() живёт всё время выполнения цикла благодаря продлению жизни через auto&& __range, поэтому сам цикл безопасен. Ловушка — если тело диапазона возвращает ссылку/итератор на что-то временное внутри выражения (например, метод, возвращающий диапазон на данные временного подобъекта) — тогда UB остаётся и в C++23-исправлении это не учтено.for (auto x : getVector()) — the temporary result of getVector() lives for the entire duration of the loop thanks to lifetime extension via auto&& __range, so the loop itself is safe. The trap is when the range expression returns a reference or iterator into something temporary nested inside it (for example, a method returning a range over data owned by a temporary subobject) — that case remains UB, and the C++23 fix doesn't cover it.
for (auto& x : v) v.push_back(x); — та же проблема, что и с ручными итераторами: push_back может реаллоцировать буфер вектора, и __begin/__end внутри цикла станут dangling. Синтаксис короче, инвалидация итераторов работает так же, как раньше.for (auto& x : v) v.push_back(x); — the same problem as with hand-written iterators: push_back may reallocate the vector's buffer, leaving __begin/__end inside the loop dangling. The syntax got shorter, but iterator invalidation works exactly as it always did.