Range-based
for
loop
(since 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」の検索がそれぞれ少なくとも1つの宣言を見つける場合、 /* 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 } |
(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
}
;
/* 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) |
範囲ベース
for
ループ(
異なる
begin
/
end
型)
|
|
202211L
|
(C++23) | 範囲初期化子 内の一時オブジェクトに対する生存期間延長 |
キーワード
例
#include <iostream> #include <vector> int main() { std::vector<int> v = {0, 1, 2, 3, 4, 5}; for (const int& i : v) // const参照によるアクセス std::cout << i << ' '; std::cout << '\n'; for (auto i : v) // 値によるアクセス、iの型はint std::cout << i << ' '; std::cout << '\n'; for (auto&& i : v) // 転送参照によるアクセス、iの型はint& std::cout << i << ' '; std::cout << '\n'; const auto& cv = v; for (auto&& i : cv) // 転送参照によるアクセス、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) // 初期化文 (C++20) std::cout << --n + i << ' '; std::cout << '\n'; for (typedef decltype(v)::value_type elem_t; elem_t i : v) // typedef宣言を初期化文として使用 (C++20) std::cout << i << ' '; std::cout << '\n'; for (using elem_t = decltype(v)::value_type; elem_t i : v) // エイリアス宣言を初期化文として使用 (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
を適用する
(関数テンプレート) |