範囲に対して
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以降)
(
/* */
で囲まれた変数と式は説明専用です):
{
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
は以下のいずれかになります:
説明専用の式
/* 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以降)
キーワード
for
例
#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
」のいずれかが存在する場合、
メンバー解釈が使用されていた
両方が存在する場合のみ使用する
関連項目
範囲内の要素に単項関数オブジェクト を適用する範囲 (関数テンプレート & アルゴリズム 関数オブジェクト)