範囲ベースの for ループ (C++11以上)
範囲に対して for ループを実行します。
コンテナ内のすべての要素などの値の範囲に対して行う、伝統的な for ループのより可読性の高い同等品として使用されます。
構文
attr(オプション) for ( range_declaration : range_expression ) loop_statement
|
(C++20未満) | |
attr(オプション) for ( init-statement(オプション)range_declaration : range_expression )
loop_statement |
(C++20以上) | |
| attr | - | 任意の個数の属性。 |
| init-statement(C++20) | - | 以下のいずれか。
|
| range_declaration | - | range_expression によって表されるシーケンスの要素の型、またはその型への参照の、名前付き変数の宣言。 しばしば自動的な型推定のための auto 指定子が使用されます。 |
| range_expression | - | 適切なシーケンス (配列、または begin および end メンバ関数または自由関数が定義されているオブジェクト (後述)) または波括弧初期化子リストを表す任意の式。
|
| loop_statement | - | 任意の文。 一般的には複文であり、ループの本体となります。 |
|
range_declaration は構造化束縛の宣言でも構いません。 for (auto&& [first,second] : mymap) {
// first と second を使う
}
|
(C++17以上) |
説明
上記の構文は以下と同等なコードになります (__range、 __begin および __end は説明専用です)。
|
|
(C++17未満) |
|
|
(C++17以上) (C++20未満) |
|
|
(C++20以上) |
range_expression がイテレートするシーケンスまたは範囲を決定するために評価されます。 シーケンス内の各要素が順番に、逆参照され、 range_declaration で指定された型と名前を持つ変数に代入されます。
begin_expr および end_expr は以下のように定義されます。
- range_expression が配列型の式の場合、
begin_exprは__rangeで、end_exprは(__range + __bound)です。 ただし__boundはその配列の要素数です (配列のサイズが不明であるか、不完全型の配列の場合、プログラムは ill-formed です)。 - range_expression が
beginという名前のメンバとendという名前のメンバの両方を持つクラス型Cの式の場合 (それらのメンバの型やアクセス可能性に関わらず)、begin_exprは__range.begin()で、end_exprは__range.end()です。 - そうでなければ、
begin_exprはbegin(__range)で、end_exprはend(__range)です。 ただし、これらは実引数依存の名前探索によってのみ探されます (非 ADL の名前探索は行われません)。
伝統的なループと同様に、ループを早期に終了するために break 文を使用することができ、次の要素からループを再開するために continue 文を使用することができます。
一時オブジェクトの範囲式
range_expression が一時オブジェクトを返した場合、その生存期間は、転送参照 __range への束縛によって表されている通り、ループの終わりまで延長されますが、 range_expression 内の一時オブジェクトの生存期間が延長されないことには注意が必要です。
for (auto& x : foo().items()) { /* .. */ } // foo() が値で返す場合は未定義動作。
|
この問題は init-statement を用いてワークアラウンドできます。 for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK。
|
(C++20以上) |
ノート
初期化子 (range_expression) が波括弧初期化子リストの場合は、 __range は std::initializer_list<>&& であると推定されます。
転送参照への推定 for (auto&& var : sequence) を用いることは、安全であり、実際、総称コードでは推奨されます。
範囲の型が begin という名前のメンバと end という名前のメンバを持つ場合はメンバの解釈が用いられます。 これはそのメンバが型であるかデータメンバであるか関数であるか列挙子であるかに関わらず、またそのアクセス可能性に関わらず行われます。 そのため、 class meow { enum { begin = 1, end = 2}; /* … */ }; のようなクラスは、たとえ名前空間スコープの begin/end 関数が存在していても、範囲ベースの for ループで使用することはできません。
range_declaration で宣言された変数は、通常、 loop_statement で使用されますが、そうしなければならないわけではありません。
C++17 以降、 begin_expr と end_expr の型が同じである必要はなく、実際、 end_expr の型がイテレータである必要はありません。 等しくないかどうかを比較できる必要があるだけです。 これは範囲を述語 (例えば「イテレータがヌル文字を指している」など) によって区切ることを可能とします。
コピーオンライトの意味論を持つ (非 const な) オブジェクトに対して使用するとき、範囲ベースの for ループは非 const な begin() メンバ関数を (暗黙に) 呼ぶことによってディープコピーを発生させる場合があります。 これが望ましくない場合 (例えばループが実際にはオブジェクトを変更しないなど) は、 std::as_const を使用することができます。
struct cow_string { /* ... */ }; // コピーオンライト文字列
cow_string str = /* ... */;
// for(auto x : str) { /* ... */ } // ディープコピーが発生するかもしれません。
for(auto x : std::as_const(str)) { /* ... */ }
キーワード
欠陥報告
以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。
| DR | 適用先 | 発行時の動作 | 正しい動作 |
|---|---|---|---|
| P0962R1 | C++11 | member interpretation is used if either member begin and end is present | only used if both are present |
例
#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';
}
出力:
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
関連項目
| 指定範囲の要素に関数を適用します (関数テンプレート) |