Range-based
for
loop
(since C++11)
범위에 대해 for 루프를 실행합니다.
범위 기반 for 루프는 값의 범위(예: 컨테이너의 모든 요소)에서 동작하는 전통적인 for loop 보다 더 가독성이 높은 대안으로 사용됩니다.
목차 |
구문
attr
(선택 사항)
for (
init-statement
(선택 사항)
item-declaration
:
range-initializer
)
statement
|
|||||||||
| attr | - | 임의의 개수의 attributes | ||
| init-statement | - |
(since C++20)
다음 중 하나
모든 init-statement 는 세미콜론으로 끝나야 합니다. 이것이 흔히 비공식적으로 표현식이나 선언 뒤에 세미콜론이 오는 것으로 설명되는 이유입니다. |
||
| item-declaration | - | 각 범위 항목에 대한 선언 | ||
| range-initializer | - | expression 또는 brace-enclosed initializer list | ||
| statement | - | 임의의 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" 이름에 대한 검색이 각각 최소한 하나의 선언을 찾는 경우, /* 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 */ 의 타입은 반복자일 필요가 없습니다: 단지 반복자와 부등식 비교가 가능하기만 하면 됩니다. 이를 통해 범위를 조건자(예: "반복자가 널 문자를 가리킴")로 구분하는 것이 가능해집니다. |
(since C++17) |
복사 시 쓰기(copy-on-write) 의미론을 가진 (non-const) 객체와 함께 사용될 때, 범위 기반
for
루프는 (암묵적으로) non-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)) { /* ... */ } |
(since 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
" 중 하나가 존재하는 경우
멤버 해석이 사용됨 |
둘 다 존재하는 경우에만 사용됨 |
참고 항목
|
범위의 요소들에 단항
함수 객체
를 적용합니다
(함수 템플릿) |