List-initialization (since C++11)
객체를 중괄호로 둘러싸인 초기화자 리스트 로부터 초기화합니다.
목차 |
구문
직접 목록 초기화
T 객체
{
arg1, arg2, ...
};
|
(1) | ||||||||
T
{
arg1, arg2, ...
}
|
(2) | ||||||||
new
T
{
arg1, arg2, ...
}
|
(3) | ||||||||
클래스
{
T 멤버
{
arg1, arg2, ...
}; };
|
(4) | ||||||||
클래스
::
클래스
() :
멤버
{
arg1, arg2, ...
} {...
|
(5) | ||||||||
복사 목록 초기화
T 객체
= {
인수1, 인수2, ...
};
|
(6) | ||||||||
함수
({
인수1, 인수2, ...
})
|
(7) | ||||||||
return {
인수1, 인수2, ...
};
|
(8) | ||||||||
객체
[{
인수1, 인수2, ...
}]
|
(9) | ||||||||
객체
= {
인수1, 인수2, ...
}
|
(10) | ||||||||
U
({
인수1, 인수2, ...
})
|
(11) | ||||||||
클래스
{
T 멤버
= {
인수1, 인수2, ...
}; };
|
(12) | ||||||||
목록 초기화는 다음과 같은 상황에서 수행됩니다:
- 직접 목록 초기화 (명시적 및 비명시적 생성자 모두 고려됨)
- 복사 목록 초기화 (명시적 및 비명시적 생성자 모두 고려되지만, 비명시적 생성자만 호출될 수 있음)
U
유형은 목록 초기화되는 유형이 아닙니다;
U
의 생성자 매개변수가 목록 초기화됩니다)
설명
(cv 한정자가 있을 수 있는)
T
타입 객체의 목록 초기화 효과는 다음과 같습니다:
| (C++20부터) |
-
만약
T가 집합 클래스이고 중괄호로 둘러싸인 초기화 리스트 (지정 초기화 리스트를 포함하지 않는) (C++20부터) 가 동일하거나 파생된 타입(possibly cv-qualified)의 단일 초기화 절을 가지고 있는 경우, 객체는 해당 초기화 절로부터 초기화됩니다 ( 복사 초기화 는 복사-리스트-초기화용, 직접 초기화 는 직접-리스트-초기화용). -
그렇지 않고 만약
T가 문자 배열이고 중괄호로 둘러싸인 초기화 리스트가 적절한 타입의 문자열 리터럴인 단일 초기화 절을 가지고 있는 경우, 배열은 일반적으로 문자열 리터럴로부터 초기화됩니다 .
-
그렇지 않고, 중괄호로 둘러싸인 초기화자 목록이 비어 있으며
T가 기본 생성자를 가진 클래스 타입인 경우, 값 초기화(value-initialization) 가 수행됩니다.
-
그렇지 않고
T가 std::initializer_list 의 특수화인 경우, 객체는 아래 에 설명된 대로 초기화됩니다.
-
그렇지 않고
T가 클래스 타입인 경우,T의 생성자들을 두 단계로 고려합니다:
-
- std::initializer_list 를 유일한 인수로 받거나, 나머지 인수들이 기본값을 가지는 경우 첫 번째 인수로 받는 모든 생성자들은 검토되며, 오버로드 해결 을 통해 std::initializer_list 타입의 단일 인수에 대해 매칭됩니다.
-
-
이전 단계에서 일치하는 생성자를 찾지 못한 경우,
T의 모든 생성자들이 오버로드 해결 에 참여하며, 중괄호로 둘러싸인 초기화 리스트의 초기화 절들로 구성된 인수 집합에 대해 비축소 변환만 허용된다는 제한이 적용됩니다. 이 단계에서 복사-리스트-초기화에 대한 최적 일치로 명시적 생성자가 선택되면 컴파일이 실패합니다 (단순 복사-초기화에서는 명시적 생성자가 전혀 고려되지 않는다는 점에 유의).
-
이전 단계에서 일치하는 생성자를 찾지 못한 경우,
| (C++17부터) |
-
그렇지 않은 경우 (즉
T가 클래스 타입이 아닌 경우), 중괄호로 둘러싸인 초기화자 목록에 단 하나의 초기화 절만 있고,T가 참조 타입이 아니거나 참조 타입인 경우 그 참조 대상 타입이 초기화 절의 타입과 동일하거나 해당 타입의 기본 클래스인 경우,T는 직접 초기화 (직접 목록 초기화에서) 또는 복사 초기화 (복사 목록 초기화에서)되지만, 축소 변환은 허용되지 않습니다.
-
그렇지 않고,
T가 초기화 절의 타입과 호환되지 않는 참조 타입인 경우:
|
(C++17까지) |
|
(C++17부터) |
-
그렇지 않고, 중괄호로 둘러싸인 초기화자 목록에 초기화 절이 없는 경우,
T는 value-initialized 됩니다.
목록 초기화 std::initializer_list
std:: initializer_list < E > 타입의 객체는 초기화 리스트로부터 마치 컴파일러가 그리고 materialized (C++17부터) “ N const E 배열” 타입의 prvalue 를 생성한 것처럼 구성됩니다. 여기서 N 은 초기화 리스트에 있는 초기화 절의 개수입니다. 이를 초기화 리스트의 backing array (백킹 배열)이라고 합니다.
백킹 배열의 각 요소는 초기화 리스트의 해당 초기화 절에 의해 copy-initialized 되며, std:: initializer_list < E > 객체는 해당 배열을 참조하도록 구성됩니다. 복사에 선택된 생성자 또는 변환 함수는 초기화 리스트의 컨텍스트에서 accessible 해야 합니다. 요소 중 하나를 초기화하는 데 narrowing conversion이 필요한 경우 프로그램은 잘못된 형식입니다.
백킹 배열(backing array)은 다른 임시 객체(temporary object) 와 동일한 수명을 가지지만, std::initializer_list 객체를 백킹 배열로부터 초기화하는 경우, 배열의 수명은 임시 객체에 대한 참조를 바인딩(binding a reference to a temporary) 할 때와 정확히 동일하게 연장됩니다.
void f(std::initializer_list<double> il); void g(float x) { f({1, x, 3}); } void h() { f({1, 2, 3}); } struct A { mutable int i; }; void q(std::initializer_list<A>); void r() { q({A{1}, A{2}, A{3}}); } // 위의 초기화는 아래와 거의 동등한 방식으로 구현될 것입니다, // 컴파일러가 initializer_list 객체를 한 쌍의 포인터로 구성할 수 있다고 가정하고, // `__b`가 `f` 호출보다 오래 지속되지 않는다는 이해를 바탕으로 합니다. void g(float x) { const double __a[3] = {double{1}, double{x}, double{3}}; // backing array f(std::initializer_list<double>(__a, __a + 3)); } void h() { static constexpr double __b[3] = {double{1}, double{2}, double{3}}; // backing array f(std::initializer_list<double>(__b, __b + 3)); } void r() { const A __c[3] = {A{1}, A{2}, A{3}}; // backing array q(std::initializer_list<A>(__c, __c + 3)); }
모든 백킹 배열이 구별되는지(즉, 겹치지 않는 객체 에 저장되는지) 여부는 명시되어 있지 않습니다:
bool fun(std::initializer_list<int> il1, std::initializer_list<int> il2) { return il2.begin() == il1.begin() + 1; } bool overlapping = fun({1, 2, 3}, {2, 3, 4}); // 결과는 명시되지 않음: // 내부 배열이 {1, 2, 3, 4} 내에서 // 저장 공간을 공유할 수 있음
축소 변환
목록 초기화는 다음과 같은 변환을 금지하여 허용되는 암시적 변환 을 제한합니다:
- 부동 소수점 타입에서 정수 타입으로의 변환
-
부동 소수점 타입
T에서 다른 부동 소수점 타입으로의 변환. 단, 대상 타입의 floating-point conversion rank 가T의 랭크보다 크지도 않고 같지도 않은 경우. 다음 조건 중 하나를 만족하는 상수 표현식인 경우는 예외:- 변환된 값이 유한하며 변환 시 오버플로우가 발생하지 않는 경우
- 변환 전과 변환 후의 값이 모두 비유한 값인 경우
- 정수형에서 부동소수점형으로의 변환, 단 원본이 대상 타입에 정확히 저장될 수 있는 상수 표현식인 경우는 제외
- 정수형 또는 비범위 열거형에서 원본의 모든 값을 표현할 수 없는 정수형으로의 변환. 단, 다음의 경우는 제외:
- 포인터 타입 또는 포인터-대-멤버 타입에서 bool 로의 변환
참고 사항
모든 초기화 절은 sequenced before 중괄호로 둘러싸인 초기화 목록에서 그 뒤에 오는 어떤 초기화 절보다 먼저 순서가 정해집니다. 이는 function call expression 의 인수들과 대조되는데, 인수들은 unsequenced (until C++17) indeterminately sequenced (since C++17) 입니다.
중괄호로 둘러싸인 초기화 리스트는 표현식이 아니므로 타입을 가지지 않습니다. 예를 들어 decltype ( { 1 , 2 } ) 는 잘못된 형식입니다. 타입이 없다는 것은 템플릿 타입 추론이 중괄호로 둘러싸인 초기화 리스트와 일치하는 타입을 추론할 수 없음을 의미합니다. 따라서 선언 template < class T > void f ( T ) ; 가 주어졌을 때, 표현식 f ( { 1 , 2 , 3 } ) 는 잘못된 형식입니다. 그러나 템플릿 매개변수는 다른 경우에는 추론될 수 있습니다. 예를 들어 std:: vector < int > v ( std:: istream_iterator < int > ( std:: cin ) , { } ) 와 같은 경우, 반복자 타입이 첫 번째 인수로 추론되지만 두 번째 매개변수 위치에서도 사용됩니다. 특별한 예외로 auto 키워드를 사용한 타입 추론 이 있으며, 이는 복사-리스트-초기화에서 모든 중괄호로 둘러싸인 초기화 리스트를 std::initializer_list 로 추론합니다.
또한 중괄호로 둘러싸인 초기화 리스트는 타입을 가지지 않기 때문에, 오버로드 해결을 위한 특별한 규칙들 이 오버로드된 함수 호출의 인수로 사용될 때 적용됩니다.
집합체는 동일한 타입의 단일 초기화 절을 가진 중괄호로 둘러싸인 초기화 목록에서 직접 복사/이동 초기화를 수행하지만, 비집합체는 std::initializer_list 생성자를 먼저 고려합니다:
struct X {}; // 집합체(aggregate) struct Q // 비집합체(non-aggregate) { Q() = default; Q(Q const&) = default; Q(std::initializer_list<Q>) {} }; int main() { X x; X x2 = X{x}; // 복사 생성자 (집합체 초기화가 아님) Q q; Q q2 = Q{q}; // 초기화 리스트 생성자 (복사 생성자가 아님) }
일부 컴파일러(예: gcc 10)는 C++20 모드에서 포인터나 멤버 포인터에서 bool 로의 변환만 축소 변환으로 간주합니다.
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_initializer_lists
|
200806L
|
(C++11) | 목록 초기화 및 std::initializer_list |
예제
#include <iostream> #include <map> #include <string> #include <vector> struct Foo { std::vector<int> mem = {1, 2, 3}; // 비정적 멤버의 목록 초기화 std::vector<int> mem2; Foo() : mem2{-1, -2, -3} {} // 생성자에서 멤버의 목록 초기화 }; std::pair<std::string, std::string> f(std::pair<std::string, std::string> p) { return {p.second, p.first}; // 반환문에서의 목록 초기화 } int main() { int n0{}; // 값 초기화(0으로) int n1{1}; // 직접 목록 초기화 std::string s1{'a', 'b', 'c', 'd'}; // initializer-list 생성자 호출 std::string s2{s1, 2, 2}; // 일반 생성자 호출 std::string s3{0x61, 'a'}; // initializer-list 생성자가 (int, char)보다 우선됨 int n2 = {1}; // 복사 목록 초기화 double d = double{1.2}; // prvalue의 목록 초기화, 이후 복사 초기화 auto s4 = std::string{"HelloWorld"}; // 위와 동일, C++17부터는 임시 객체 생성되지 않음 // created since C++17 std::map<int, std::string> m = // 중첩 목록 초기화 { {1, "a"}, {2, {'a', 'b', 'c'}}, {3, s1} }; std::cout << f({"hello", "world"}).first // 함수 호출에서의 목록 초기화 << '\n'; const int (&ar)[2] = {1, 2}; // 임시 배열에 lvalue 참조를 바인딩 int&& r1 = {1}; // 임시 int에 rvalue 참조를 바인딩 // int& r2 = {2}; // 오류: rvalue를 비 const lvalue 참조에 바인딩할 수 없음 // int bad{1.0}; // 오류: 축소 변환 unsigned char uc1{10}; // 정상 // unsigned char uc2{-1}; // 오류: 축소 변환 Foo f; std::cout << n0 << ' ' << n1 << ' ' << n2 << '\n' << s1 << ' ' << s2 << ' ' << s3 << '\n'; for (auto p : m) std::cout << p.first << ' ' << p.second << '\n'; for (auto n : f.mem) std::cout << n << ' '; for (auto n : f.mem2) std::cout << n << ' '; std::cout << '\n'; [](...){}(d, ar, r1, uc1); // [[maybe_unused]]의 효과를 가짐 }
출력:
world 0 1 1 abcd cd aa 1 a 2 abc 3 abcd 1 2 3 -1 -2 -3
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 수정된 동작 |
|---|---|---|---|
| CWG 1288 | C++11 |
단일 초기화 절을 가진 중괄호 초기화 목록으로 참조를 목록 초기화할 때
항상 임시 객체에 바인딩됨 |
유효한 경우 해당 초기화 절에
바인딩 |
| CWG 1290 | C++11 | 백업 배열의 수명이 올바르게 명시되지 않음 |
다른 임시 객체와 동일하게
명시 |
| CWG 1324 | C++11 |
{}
로부터의 초기화에서 초기화를 먼저 고려함
|
집계 초기화를
먼저 고려 |
| CWG 1418 | C++11 | 백업 배열의 타입에 const 가 없음 | const 추가됨 |
| CWG 1467 | C++11 |
동일 타입 집계 및 문자 배열 초기화가 금지됨; 단일 절 목록에서
초기화 목록 생성자가 복사 생성자보다 우선순위를 가짐 |
동일 타입 초기화
허용; 단일 절 목록은 직접 초기화 |
| CWG 1494 | C++11 |
호환되지 않는 타입의 초기화 절로 참조를 목록 초기화할 때
생성된 임시 객체가 직접 목록 초기화되는지 복사 목록 초기화되는지 명시되지 않음 |
참조에 대한 초기화
종류에 따라 결정됨 |
| CWG 2137 | C++11 |
{X}
로부터
X
를 목록 초기화할 때 초기화 목록 생성자가
복사 생성자에 밀림 |
비집계는 초기화 목록을
먼저 고려 |
| CWG 2252 | C++17 | 열거형이 비스칼라 값으로부터 목록 초기화될 수 있음 | 금지됨 |
| CWG 2267 | C++11 |
CWG 이슈 1494
의 해결책이
임시 객체가 직접 목록 초기화될 수 있음을 명확히 함 |
참조 목록 초기화 시
복사 목록 초기화됨 |
| CWG 2374 | C++17 | 열거형의 직접 목록 초기화에서 너무 많은 소스 타입이 허용됨 | 제한됨 |
| CWG 2627 | C++11 |
더 큰 정수 타입의 좁은 비트 필드는 더 작은 정수 타입으로
승격될 수 있지만 여전히 축소 변환이었음 |
축소 변환이
아님 |
| CWG 2713 | C++20 |
집계 클래스에 대한 참조를 지정 초기화 목록으로
초기화할 수 없음 |
허용됨 |
| CWG 2830 | C++11 | 목록 초기화가 최상위 cv-한정자를 무시하지 않음 | 무시함 |
| CWG 2864 | C++11 | 오버플로우되는 부동소수점 변환이 축소 변환이 아니었음 | 축소 변환임 |
| P1957R2 | C++11 |
포인터/멤버 포인터에서
bool
로의 변환이
축소 변환이 아니었음 |
축소 변환으로 간주됨 |
| P2752R3 | C++11 | 수명이 겹치는 백업 배열이 겹칠 수 없었음 | 겹칠 수 있음 |