Цикл for с диапазоном (с C++11)
Выполняет цикл for над диапазоном.
Используется в качестве более читаемой альтернативы традиционному циклу for, работающему с диапазоном значений, например, со всеми элементами контейнера.
Синтаксис
attr (необязательно) for ( init-statement (необязательно) range-declaration : range-expression ) loop-statement |
| attr | - | любое количество атрибутов | ||
| init-statement | - | (с C++20) либо
;, поэтому его часто неофициально описывают как выражение или объявление, за которым следует точка с запятой. |
||
| range-declaration | - | объявление именованной переменной, тип которой является типом элемента последовательности, представленной range-expression, или ссылкой на этот тип. Часто использует спецификатор auto для автоматического вывода типа | ||
| range-expression | - | любое выражение, которое представляет собой подходящую последовательность (массив или объект, для которых begin и end члены или свободные функции определены, см. ниже) или список инициализации. |
||
| loop-statement | - | любой оператор, как правило, составной оператор, который является телом цикла |
| range-declaration может быть объявлением структурированной связи: for (auto&& [first, second] : mymap)
{
// use first and second
} | (с C++17) |
Объяснение
Приведённый выше синтаксис генерирует код, эквивалентный следующему за исключением расширения срока службы временных значений range-expression (см. ниже)(с C++23). Переменные __range, __begin и __end предназначены только для иллюстрации.
|
| (до C++17) |
|
|
(с C++17) (до C++20) |
|
| (с C++20) |
range-expression вычисляется для определения последовательности или диапазона для итерации. Каждый элемент последовательности поочерёдно ссылается и используется для инициализации переменной с типом и именем, указанными в range-declaration.
begin-expr и end-expr определяются следующим образом:
- Если range-expression является выражением типа массива, то
begin-exprравно__rangeиend-exprравно(__range + __bound), где__bound— количество элементов в массиве (если массив имеет неизвестный размер или является неполного типа, программа недействительна); - Если range-expression является выражением типа класса
C, который имеет оба члена, названныеbeginиend(независимо от типа или доступности такого члена), тоbegin-exprравно__range.begin()иend-exprравно__range.end(); - В противном случае,
begin-exprравноbegin(__range)иend-exprравноend(__range), которые находятся с помощью поиска аргументов (поиск без ADL не выполняется).
Так же как и с традиционным циклом, оператор break может использоваться для выхода из цикла досрочно, а оператор continue может использоваться для возобновления цикла с следующим элементом.
Если имя, введенное в init-statement, повторно объявляется во внешнем блоке loop-statement, программа некорректна:
for (int i : {1, 2, 3})
int i = 1; // error: redeclarationВременное выражение диапазона
Если range-expression возвращает временное значение, его срок действия продлевается до конца цикла, как указано связыванием с передающим ссылкой __range. Срок жизни всех временных значений внутри range-expression не(до C++23) продлевается если бы они в противном случае были уничтожены в конце range-expression(с C++23).
// if foo() returns by value
for (auto& x : foo().items()) { /* .. */ } // until C++23 undefined behavior| Эту проблему можно обойти, используя init-statement: for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK | (с C++20) |
| Обратите внимание, что даже в C++23 нессылочные параметры промежуточных вызовов функций не получают продления срока жизни (поскольку в некоторых ABIs они уничтожаются в вызываемой функции, а не в вызывающей), но это проблема только для функций, которые сами по себе некорректны: 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
} | (с C++23) |
Примечания
Если инициализатор (range-expression) является списком инициализации, __range выводится как std::initializer_list<>&&.
Безопасно, и на самом деле предпочтительно в обобщенном коде, использовать вывод для передачи ссылки, for (auto&& var : sequence).
Интерпретация члена используется, если тип диапазона имеет член с именем begin и член с именем end. Это делается независимо от того, является ли член типом, данными, функцией или перечислением, и независимо от его доступности. Таким образом, класс, подобный class meow { enum { begin = 1, end = 2 }; /* rest of class */ };, не может быть использован с циклом for по диапазону, даже если функции begin/end в области имен присутствуют.
Хотя переменная, объявленная в range-declaration, обычно используется в loop-statement, это не обязательно.
| Начиная с C++17, типы | (с C++17) |
При использовании с объектом (не константным), имеющим семантику копирования при записи, цикл for с диапазоном может вызвать глубокую копию, (неявным образом) вызвав функцию члена begin().
| Если это нежелательно (например, потому что цикл фактически не изменяет объект), можно избежать этого, используя 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-expression |
Ключевые слова
Пример
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v = {0, 1, 2, 3, 4, 5};
for (const int& i : v) // access by const reference
std::cout << i << ' ';
std::cout << '\n';
for (auto i : v) // access by value, the type of i is int
std::cout << i << ' ';
std::cout << '\n';
for (auto&& i : v) // access by forwarding reference, the type of i is int&
std::cout << i << ' ';
std::cout << '\n';
const auto& cv = v;
for (auto&& i : cv) // access by f-d reference, the type of i is const int&
std::cout << i << ' ';
std::cout << '\n';
for (int n : {0, 1, 2, 3, 4, 5}) // the initializer may be a braced-init-list
std::cout << n << ' ';
std::cout << '\n';
int a[] = {0, 1, 2, 3, 4, 5};
for (int n : a) // the initializer may be an array
std::cout << n << ' ';
std::cout << '\n';
for ([[maybe_unused]] int n : a)
std::cout << 1 << ' '; // the loop variable need not be used
std::cout << '\n';
for (auto n = v.size(); auto i : v) // the init-statement (C++20)
std::cout << --n + i << ' ';
std::cout << '\n';
for (typedef decltype(v)::value_type elem_t; elem_t i : v)
// typedef declaration as init-statement (C++20)
std::cout << i << ' ';
std::cout << '\n';
for (using elem_t = decltype(v)::value_type; elem_t i : v)
// alias declaration as 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, могли быть повторно объявлены | в этом случае программа является неправильной |
| P0962R1 | C++11 | интерпретация члена использовалась, если либо член begin и end присутствует | используется только если оба присутствуют |
См. также
| применяет функцию к диапазону элементов (шаблон функции) |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/range-for