Цикл for по диапазону (начиная с C++11)
Выполняет цикл for по диапазону.
Используется как более удобочитаемый аналог традиционного цикла for, работающего с диапазоном значений, например со всеми элементами контейнера.
Синтаксис
attr (необязательно) for ( init-statement (необязательно) item-declaration : range-initializer ) statement |
| attr | - | любое количество атрибутов | ||
| init-statement | - |
(начиная с C++20) один из следующих вариантов:
Обратите внимание, что любой init-statement должен заканчиваться точкой с запятой. Поэтому его часто неформально описывают как выражение или объявление, за которым следует точка с запятой. |
||
| item-declaration | - | объявление для каждого элемента диапазона | ||
| range-initializer | - | выражение или список инициализации в фигурных скобках | ||
| statement | - | любой оператор (обычно составной оператор) |
Пояснение
Приведённый выше синтаксис создаёт код, эквивалентный следующему за исключением продления времени жизни временных объектов в range-initializer (см. ниже)(начиная с C++23) (переменные и выражения, заключённые в /* */, приведены только для наглядности):
|
| (до C++17) |
|
|
(начиная с C++17) (до C++20) |
|
| (начиная с C++20) |
range-initializer вычисляется для инициализации последовательности или диапазона, по которому выполняется итерация. Каждый элемент последовательности по очереди разыменовывается и используется для инициализации переменной с типом и именем, указанными в item-declaration.
item-declaration может быть одним из следующих вариантов:
- простое объявление со следующими ограничениями:
- Оно содержит только один декларатор.
- Декларатор не должен иметь инициализатора.
- Последовательность спецификаторов объявления может содержать только спецификаторы типа и constexpr и не может определять класс или перечисление.
Выражения, приведённые только для наглядности, /* 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, для этого можно использовать оператор continue.
Если имя, введённое в init-statement, повторно объявляется во внешнем блоке statement, программа некорректна:
for (int i : {1, 2, 3})
int i = 1; // error: redeclaration
Временный объект в инициализаторе диапазона
Если range-initializer возвращает временный объект, время его жизни продлевается до конца цикла, что обеспечивается привязкой к универсальной ссылке /* range */.
Время жизни всех временных объектов внутри range-initializer не продлевается, если только в противном случае они не уничтожались бы в конце range-initializer(начиная с 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 время жизни параметров промежуточных вызовов функций, не являющихся ссылками, не продлевается (поскольку в некоторых 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
}
| (начиная с C++23) |
Примечания
Если range-initializer — это список инициализации в фигурных скобках, тип /* range */ выводится как ссылка на std::initializer_list.
В обобщённом коде безопасно и предпочтительно использовать вывод типа для универсальной ссылки, for (auto&& var : sequence).
Интерпретация через члены используется, если тип диапазона имеет член с именем «begin» и член с именем «end». Это применяется независимо от того, является ли член типом, членом-данными, функцией или перечислителем, а также независимо от его доступности. Поэтому такой класс, как class meow { enum { begin = 1, end = 2 }; /* rest of class */ };, нельзя использовать в цикле for по диапазону, даже если присутствуют функции «begin»/«end» в области видимости пространства имён.
Хотя переменная, объявленная в item-declaration, обычно используется в statement, это не является обязательным.
| Начиная с C++17 типы /* begin-expr */ и /* end-expr */ не обязаны совпадать; более того, тип /* end-expr */ не обязательно должен быть итератором: достаточно, чтобы его можно было сравнить с итератором на неравенство. Это позволяет ограничивать диапазон предикатом (например, «итератор указывает на нулевой символ»). | (начиная с 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-initializer |
Ключевые слова
Пример
#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-enclosed initializer 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, можно было объявить повторно | в этом случае программа некорректна |
| CWG 2825 | C++11 | если range-initializer является списком инициализации в фигурных скобках, будет выполнен поиск функций-нечленов « begin» и «end» | в этом случае будет выполнен поиск членов «begin»и « end» |
| P0962R1 | C++11 | интерпретация через члены использовалась, если присутствовал хотя бы один из членов « begin» и «end» | используется только в том случае, если присутствуют оба члена |
См. также
| применяет унарный функциональный объект к элементам диапазона (шаблон функции и функциональный объект-алгоритм) |
|
|
(C++20) |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/range-for