Namespaces
Variants

Range-based for loop (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
for
range- for (C++11)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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

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

Содержание

Синтаксис

attr  (необязательно) for ( init-statement  (необязательно) item-declaration : range-initializer ) statement
attr - любое количество атрибутов
init-statement - (since C++20) один из
(since C++23)

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

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

Объяснение

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

{

auto&& /* range */ = инициализатор-диапазона  ;
for (auto /* begin */ = /* begin-expr */ , /* end */ = /* end-expr */ ;
/* begin */ != /* end */ ; ++ /* begin */ )
{
объявление-элемента = * /* begin */ ;
оператор
}

}

(до C++17)

{

auto&& /* range */ = инициализатор-диапазона  ;
auto /* begin */ = /* begin-expr */ ;
auto /* end */ = /* end-expr */ ;
for ( ; /* begin */ != /* end */ ; ++ /* begin */ )
{
объявление-элемента = * /* begin */ ;
оператор
}

}

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

{

инициализирующий-оператор
auto&& /* range */ = инициализатор-диапазона  ;
auto /* begin */ = /* begin-expr */ ;
auto /* end */ = /* end-expr */ ;
for ( ; /* begin */ != /* end */ ; ++ /* begin */ )
{
объявление-элемента = * /* begin */ ;
оператор
}

}

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

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

for

Пример

#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)