Spec-Zone.ru › C++

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

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

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

Синтаксис

attr (необязательно) for ( init-statement (необязательно) item-declaration : range-initializer ) statement
attr - любое количество атрибутов
init-statement - (начиная с C++20) один из следующих вариантов:
  • выражение-оператор (который может быть пустым оператором ;)
  • простое объявление (обычно объявление переменной с инициализатором); оно может объявлять любое количество переменных или быть объявлением структурированной привязки
  • объявление псевдонима
(начиная с C++23)

Обратите внимание, что любой init-statement должен заканчиваться точкой с запятой. Поэтому его часто неформально описывают как выражение или объявление, за которым следует точка с запятой.

item-declaration - объявление для каждого элемента диапазона
range-initializer - выражение или список инициализации в фигурных скобках
statement - любой оператор (обычно составной оператор)

Пояснение

Приведённый выше синтаксис создаёт код, эквивалентный следующему за исключением продления времени жизни временных объектов в range-initializer (см. ниже)(начиная с C++23) (переменные и выражения, заключённые в /* */, приведены только для наглядности):

{

auto&& /* range */ = range-initializer ;
for (auto /* begin */ = /* begin-expr */, /* end */ = /* end-expr */;
/* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
statement
}

}

(до C++17)

{

auto&& /* range */ = range-initializer ;
auto /* begin */ = /* begin-expr */;
auto /* end */ = /* end-expr */;
for ( ; /* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
statement
}

}

(начиная с C++17)
(до C++20)

{

init-statement
auto&& /* range */ = range-initializer ;
auto /* begin */ = /* begin-expr */;
auto /* end */ = /* end-expr */;
for ( ; /* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
statement
}

}

(начиная с 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().

Если это нежелательно (например, когда цикл фактически не изменяет объект), этого можно избежать, используя 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

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

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

См. также

for_each
применяет унарный функциональный объект к элементам диапазона
(шаблон функции и функциональный объект-алгоритм)
ranges::for_each
(C++20)

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

Spec-Zone.ru

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