Range-based
for
loop
(since C++11)
Выполняет цикл for по диапазону.
Используется как более читаемая альтернатива традиционному for циклу для работы с диапазоном значений, например, со всеми элементами контейнера.
Содержание |
Синтаксис
attr
(необязательно)
for (
init-statement
(необязательно)
item-declaration
:
range-initializer
)
statement
|
|||||||||
| attr | - | любое количество атрибутов | ||
| init-statement | - |
(since C++20)
один из
Обратите внимание, что любой init-statement должен заканчиваться точкой с запятой. Поэтому его часто неформально описывают как выражение или объявление, за которым следует точка с запятой. |
||
| item-declaration | - | объявление для каждого элемента диапазона | ||
| range-initializer | - | expression или brace-enclosed initializer list | ||
| statement | - | любой statement (обычно составной оператор) |
Объяснение
Приведённый выше синтаксис генерирует код, эквивалентный следующему за исключением расширения времени жизни временных объектов range-initializer (см. ниже ) (начиная с C++23) (переменные и выражения, заключённые в /* */ , приведены только для демонстрации):
|
|
(до C++17) |
|
|
(начиная с C++17)
(до C++20) |
|
|
(начиная с C++20) |
range-initializer вычисляется для инициализации последовательности или диапазона для итерации. Каждый элемент последовательности, в свою очередь, разыменовывается и используется для инициализации переменной с типом и именем, указанными в item-declaration .
item-declaration может быть одним из следующих:
- Простая декларация simple declaration со следующими ограничениями:
- Имеет только один declarator .
- Декларатор не должен иметь initializer .
- Последовательность спецификаторов объявления declaration specifier sequence может содержать только спецификаторы типа и constexpr , и не может определять class или enumeration .
Экспозиционные выражения /* begin-expr */ и /* end-expr */ определяются следующим образом:
-
Если тип
/* range */
является ссылкой на тип массива
R:
-
-
Если
Rимеет границу N , /* begin-expr */ является /* range */ , а /* end-expr */ является /* range */ + N . -
Если
Rявляется массивом неизвестной границы или массивом неполного типа, программа является некорректной.
-
Если
-
Если тип
/* range */
является ссылкой на тип класса
C, и поиск в области видимостиCдля имён "begin" и "end" находит как минимум одно объявление каждого, тогда /* begin-expr */ это /* range */ . begin ( ) и /* end-expr */ это /* range */ . end ( ) . -
В противном случае,
/* begin-expr */
это
begin
(
/* range */
)
и
/* end-expr */
это
end
(
/* range */
)
, где "
begin" и "end" находятся через поиск, зависимый от аргументов (поиск без ADL не выполняется).
Если цикл необходимо прервать внутри statement , в качестве завершающей инструкции можно использовать break statement .
Если текущую итерацию необходимо прервать внутри statement , можно использовать continue statement в качестве сокращения.
Если имя, введённое в init-statement , переобъявляется в самом внешнем блоке statement , программа является некорректной:
for (int i : {1, 2, 3}) int i = 1; // ошибка: повторное объявление
Инициализатор временного диапазона
Если range-initializer возвращает временный объект, время его жизни продлевается до конца цикла, как указано при связывании с пересылаемой ссылкой /* range */ .
Время жизни всех временных объектов в range-initializer не продлевается за исключением случаев, когда они должны быть уничтожены в конце range-initializer (начиная с C++23) .
// если foo() возвращает по значению for (auto& x : foo().items()) { /* ... */ } // до C++23 неопределённое поведение
|
Данную проблему можно обойти с помощью init-statement : for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK |
(начиная с C++20) |
|
Обратите внимание, что даже в C++23 параметры-не-ссылки промежуточных вызовов функций не получают расширения времени жизни (поскольку в некоторых ABI они уничтожаются в вызываемой функции, а не в вызывающей), но это является проблемой только для функций, которые в любом случае содержат ошибки: using T = std::list<int>; const T& f1(const T& t) { return t; } const T& f2(T t) { return t; } // always returns a dangling reference T g(); void foo() { for (auto e : f1(g())) {} // OK: lifetime of return value of g() extended for (auto e : f2(g())) {} // UB: lifetime of f2's value parameter ends early } |
(since C++23) |
Примечания
Если range-initializer является braced-enclosed initializer list , /* range */ выводится как ссылка на std::initializer_list .
Безопасно и, фактически, предпочтительно в обобщённом коде использовать выведение для пересылающей ссылки, for ( auto && var : sequence ) .
Интерпретация через члены используется, если тип диапазона имеет член с именем "
begin
" и член с именем "
end
". Это происходит независимо от того, является ли член типом, полем, функцией или перечислением, и независимо от его доступности. Таким образом, класс вида
class
meow
{
enum
{
begin
=
1
, end
=
2
}
;
/* остальная часть класса */
}
;
не может использоваться с циклом for на основе диапазона, даже если присутствуют функции "
begin
"/"
end
" в области видимости пространства имен.
Хотя переменная, объявленная в item-declaration , обычно используется в statement , это не является обязательным требованием.
|
Начиная с C++17, типы /* begin-expr */ и /* end-expr */ не обязаны быть одинаковыми, и фактически тип /* end-expr */ не обязан быть итератором: он должен лишь поддерживать проверку на неравенство с итератором. Это позволяет задавать диапазон с помощью предиката (например, "итератор указывает на нулевой символ"). |
(since C++17) |
При использовании с (неконстантным) объектом, имеющим семантику копирования при записи, цикл
for
по диапазону может вызвать глубокое копирование путем (неявного) вызова неконстантной функции-члена
begin()
.
|
Если это нежелательно (например, потому что цикл фактически не изменяет объект), этого можно избежать, используя std::as_const : struct cow_string { /* ... */ }; // a copy-on-write string cow_string str = /* ... */; // for (auto x : str) { /* ... */ } // may cause deep copy for (auto x : std::as_const(str)) { /* ... */ } |
(начиная с C++17) |
| Макрос тестирования возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_range_based_for
|
200907L
|
(C++11) | Цикл for на основе диапазона |
201603L
|
(C++17) |
Цикл for на основе диапазона с
различными типами
begin
/
end
|
|
202211L
|
(C++23) | Расширение времени жизни для всех временных объектов в range-initializer |
Ключевые слова
Пример
#include <iostream> #include <vector> int main() { std::vector<int> v = {0, 1, 2, 3, 4, 5}; for (const int& i : v) // доступ по константной ссылке std::cout << i << ' '; std::cout << '\n'; for (auto i : v) // доступ по значению, тип i - int std::cout << i << ' '; std::cout << '\n'; for (auto&& i : v) // доступ по forwarding reference, тип i - int& std::cout << i << ' '; std::cout << '\n'; const auto& cv = v; for (auto&& i : cv) // доступ по f-d reference, тип i - const int& std::cout << i << ' '; std::cout << '\n'; for (int n : {0, 1, 2, 3, 4, 5}) // инициализатором может быть // список инициализации в фигурных скобках std::cout << n << ' '; std::cout << '\n'; int a[] = {0, 1, 2, 3, 4, 5}; for (int n : a) // инициализатором может быть массив std::cout << n << ' '; std::cout << '\n'; for ([[maybe_unused]] int n : a) std::cout << 1 << ' '; // переменная цикла не обязана использоваться std::cout << '\n'; for (auto n = v.size(); auto i : v) // init-statement (C++20) std::cout << --n + i << ' '; std::cout << '\n'; for (typedef decltype(v)::value_type elem_t; elem_t i : v) // объявление typedef как init-statement (C++20) std::cout << i << ' '; std::cout << '\n'; for (using elem_t = decltype(v)::value_type; elem_t i : v) // объявление псевдонима как init-statement (C++23) std::cout << i << ' '; std::cout << '\n'; }
Вывод:
0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 1 1 1 1 1 1 5 5 5 5 5 5 0 1 2 3 4 5 0 1 2 3 4 5
Отчеты о дефектах
Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 1442 | C++11 |
было не указано, включает ли поиск нечленных
"
begin
" и "
end
" обычный неквалифицированный поиск
|
обычный неквалифицированный поиск не выполняется |
| CWG 2220 | C++11 | имена, введённые в init-statement могли быть переобъявлены | программа является некорректной в этом случае |
| CWG 2825 | C++11 |
если
range-initializer
является списком инициализации в фигурных скобках,
будут искаться нечленные "
begin
" и "
end
"
|
будет выполняться поиск членных "
begin
"
и "
end
" в этом случае
|
| P0962R1 | C++11 |
интерпретация через члены использовалась, если присутствует
либо член "
begin
", либо "
end
"
|
используется только если присутствуют оба |
Смотрите также
|
применяет унарный
function object
к элементам из
range
(function template) |