範囲ベースのforループ (C++11以降)
範囲に対してforループを実行する。
コンテナ内のすべての要素など、値の範囲に対して操作する従来のforループの、より読みやすい等価物として使用される。
目次 |
[編集] 構文
attr (optional) for ( init-statement (optional) 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 は次のいずれかである。
説明専用の式/* begin-expr */と/* end-expr */は次のように定義される。
- /* range */の型が配列型
Rへの参照である場合
Rが境界Nの場合、/* begin-expr */は/* range */であり、/* end-expr */は/* range */ + Nである。Rが未知の境界の配列または不完全な型の配列である場合、プログラムは不適格である。
- /* range */の型がクラス型
Cへの参照であり、Cのスコープ内で「begin」と「end」という名前を検索するとそれぞれ少なくとも1つの宣言が見つかる場合、/* 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 */ };のようなクラスは、名前空間スコープの「begin」/「end」関数が存在しても、範囲ベースのforループでは使用できない。
item-declarationで宣言された変数は通常statementで使用されるが、その必要はない。
|
C++17以降、/* begin-expr */と/* end-expr */の型は同じである必要はなく、実際、/* end-expr */の型はイテレータである必要もない。単に、一方と比較して不等比較できる必要がある。これにより、述語によって範囲を区切ることができる (例:「イテレータがヌル文字を指している」)。 |
(C++17以降) |
コピーオンライトセマンティクスを持つ (非const) オブジェクトで使用する場合、範囲ベースのforループは、(暗黙的に) 非constの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) | 異なるbegin/end型を持つ範囲ベースのforループ | |
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」が存在する |
両方が存在する場合のみ使用される |
[編集] 関連項目
| 範囲内の要素に単項関数オブジェクトを適用する (関数テンプレート) |