Spec-Zone.ru › C++

Цикл for с диапазоном (с C++11)

Выполняет цикл for над диапазоном.

Используется в качестве более читаемой альтернативы традиционному циклу for, работающему с диапазоном значений, например, со всеми элементами контейнера.

Синтаксис

attr (необязательно) for ( init-statement (необязательно) range-declaration : range-expression )

loop-statement

attr - любое количество атрибутов
init-statement - (с C++20) либо
  • оператор выражения (который может быть пустым оператором ";")
  • простое объявление, как правило, объявление переменной с инициализатором, но оно может объявлять произвольно много переменных или быть объявлением структурированной связи
  • объявление псевдонима типа
(с C++23)
Обратите внимание, что любой init-statement должен заканчиваться точкой с запятой ;, поэтому его часто неофициально описывают как выражение или объявление, за которым следует точка с запятой.
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 предназначены только для иллюстрации.

{

auto && __range = range-expression ;
for (auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin)
{
range-declaration = *__begin;
loop-statement
}

}

(до C++17)

{

auto && __range = range-expression ;
auto __begin = begin-expr ;
auto __end = end-expr ;
for ( ; __begin != __end; ++__begin)
{
range-declaration = *__begin;
loop-statement
}

}

(с C++17)
(до C++20)

{

init-statement
auto && __range = range-expression ;
auto __begin = begin-expr ;
auto __end = end-expr ;
for ( ; __begin != __end; ++__begin)
{
range-declaration = *__begin;
loop-statement
}

}

(с 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, типы begin-expr и end-expr не обязательно должны быть одинаковыми, и на самом деле тип end-expr не обязательно должен быть итератором: он просто должен быть сравнимым по неравенству с одним. Это позволяет ограничить диапазон с помощью предиката (например, «итератор указывает на нулевой символ»).

(с 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-expression

Ключевые слова

for

Пример

#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 присутствует
используется только если оба присутствуют

См. также

for_each
применяет функцию к диапазону элементов
(шаблон функции)

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/range-for

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API