Structured binding declaration (since C++17)
지정된 이름들을 초기화자의 하위 객체 또는 요소에 바인딩합니다.
참조와 마찬가지로, 구조적 바인딩은 기존 객체에 대한 별칭입니다. 참조와 달리, 구조적 바인딩은 반드시 참조 타입일 필요는 없습니다.
attr
(선택 사항)
decl-specifier-seq
ref-qualifier
(선택 사항)
[
sb-identifier-list
]
initializer
;
|
|||||||||
| attr | - | 임의 개수의 속성 시퀀스 | ||
| decl-specifier-seq | - |
다음 지정자들의 시퀀스 (
단순 선언
규칙 따름):
|
||
| ref-qualifier | - |
&
또는
&&
중 하나
|
||
| sb-identifier-list | - | 이 선언으로 도입되는 쉼표로 구분된 식별자 목록 , 각 식별자 뒤에는 속성 지정자 시퀀스 가 올 수 있음 (C++26부터) | ||
| initializer | - | 초기화자 (아래 참조) |
initializer
는 다음 중 하나일 수 있습니다:
=
표현식
|
(1) | ||||||||
{
표현식
}
|
(2) | ||||||||
(
표현식
)
|
(3) | ||||||||
| expression | - | 임의의 표현식 (괄호로 묶이지 않은 comma expressions 제외) |
구조화된 바인딩 선언은
sb-identifier-list
내의 모든 식별자를 주변 범위에서 이름으로 도입하고 이를
expression
이 나타내는 객체의 하위 객체나 요소에 바인딩합니다. 이렇게 도입된 바인딩을
structured bindings
(구조화된 바인딩)이라고 합니다.
|
sb-identifier-list 내의 식별자 중 하나는 생략표(...)로 시작할 수 있습니다. 이러한 식별자는 structured binding pack 을 도입합니다. 해당 식별자는 templated entity 를 선언해야 합니다. |
(since C++26) |
구조적 바인딩은 sb-identifier-list 에서 생략표시 앞에 오지 않는 식별자이거나, 동일한 식별자 목록에서 도입된 구조적 바인딩 팩의 요소 (C++26부터) 입니다.
목차 |
바인딩 프로세스
구조적 바인딩 선언은 먼저 초기화식의 값을 보유하기 위해 고유한 이름의 변수(여기서는 e 로 표시됨)를 도입합니다. 다음과 같습니다:
-
만약
expression
이 배열 타입
cv1
A를 가지고 있고 ref-qualifier 가 존재하지 않는 경우, e 를 attr (optional) specifiersA e;로 정의합니다. 여기서 specifiers 는 decl-specifier-seq 내의 지정자들 중 auto 를 제외한 시퀀스입니다.
-
그런 다음
e
의 각 요소는
expression
의 해당 요소로부터
initializer
의 형태에 따라 다음과 같이 초기화됩니다:
- 초기화 구문 (1) 의 경우, 요소들은 copy-initialized 됩니다.
- 초기화 구문 (2,3) 의 경우, 요소들은 direct-initialized 됩니다.
-
그렇지 않으면,
e
를
attr
(optional)
decl-specifier-seq
ref-qualifier
(optional)
einitializer ;로 정의합니다.
우리는 식별자 표현식
e
의 타입을 나타내기 위해
E
를 사용합니다
(즉,
E
는
std::
remove_reference_t
<
decltype
(
(
e
)
)
>
에 해당합니다).
구조적 바인딩 크기 는 구조적 바인딩 선언에 의해 도입되어야 하는 구조적 바인딩의 개수입니다.
|
sb-identifier-list
내 식별자의 개수는
|
(until C++26) |
|
sb-identifier-list
내 식별자의 개수를
N
으로,
|
(since C++26) |
struct C { int x, y, z; }; template<class T> void now_i_know_my() { auto [a, b, c] = C(); // 정상: a, b, c는 각각 x, y, z를 참조 auto [d, ...e] = C(); // 정상: d는 x를 참조; ...e는 y와 z를 참조 auto [...f, g] = C(); // 정상: ...f는 x와 y를 참조; g는 z를 참조 auto [h, i, j, ...k] = C(); // 정상: 패킷 k는 비어 있음 auto [l, m, n, o, ...p] = C(); // 오류: 구조화된 바인딩 크기가 너무 작음 }
구조적 바인딩 선언은 다음 세 가지 방법 중 하나로 바인딩을 수행하며, 이는
E
에 따라 달라집니다:
-
경우 1:
E가 배열 타입인 경우, 이름들은 배열 요소들에 바인딩됩니다. -
경우 2:
E가 비-공용체 클래스 타입이고 std:: tuple_size < E > 가value라는 멤버를 가진 완전한 타입인 경우(해당 멤버의 타입이나 접근성과 무관하게), "튜플-유사" 바인딩 프로토콜이 사용됩니다. -
경우 3:
E가 비-공용체 클래스 타입이지만 std:: tuple_size < E > 가 완전한 타입이 아닌 경우, 이름들은E의 접근 가능한 데이터 멤버들에 바인딩됩니다.
세 가지 경우 각각에 대해 아래에서 더 자세히 설명합니다.
각 구조화된 바인딩에는 아래 설명에서 정의된
참조 타입
이 있습니다. 이 타입은 괄호 없는 구조화된 바인딩에
decltype
을 적용했을 때 반환되는 타입입니다.
사례 1: 배열 바인딩
sb-identifier-list
내의 각 구조적 바인딩은 배열의 해당 요소를 참조하는 lvalue의 이름이 됩니다. 구조적 바인딩의 크기는
E
의 배열 요소 개수와 동일합니다.
각 구조적 바인딩의
참조된 타입
은 배열 요소 타입입니다. 배열 타입
E
가 cv-qualified인 경우, 그 요소 타입도 cv-qualified라는 점에 유의하십시오.
int a[2] = {1, 2}; auto [x, y] = a; // e[2]를 생성하고 a를 e로 복사한 다음, // x는 e[0]을 참조하고 y는 e[1]을 참조함 auto& [xr, yr] = a; // xr은 a[0]을 참조하고 yr은 a[1]을 참조함
Case 2: 튜플 연산을 구현하는 타입 바인딩
표현식
std::
tuple_size
<
E
>
::
value
는 올바른 형태의
정수 상수 표현식
이어야 하며,
E
의 구조적 바인딩 크기는
std::
tuple_size
<
E
>
::
value
와 동일해야 합니다.
각 구조적 바인딩에 대해 "참조 std:: tuple_element < I, E > :: type " 타입의 변수가 도입됩니다: 해당 초기화자가 lvalue인 경우 lvalue 참조, 그렇지 않으면 rvalue 참조입니다. I 번째 변수의 초기화자는 다음과 같습니다.
-
e.
get
<
I
>
(
)
,
E의 범위에서 식별자get을 클래스 멤버 접근 조회로 검색할 때 첫 번째 템플릿 매개변수가 상수 매개변수인 함수 템플릿 선언을 하나 이상 발견하는 경우 - 그렇지 않으면, get < I > ( e ) , 여기서 get 은 비 ADL 조회를 무시하고 인수 종속 조회 에 의해서만 검색됨
이러한 초기화 표현식에서,
e
는 엔티티
e
의 타입이 lvalue 참조인 경우 lvalue입니다(이는
ref-qualifier
가
&
이거나
&&
이면서 초기화 표현식이 lvalue인 경우에만 발생합니다). 그렇지 않은 경우 xvalue입니다(이는 효과적으로 일종의 완벽한 전달을 수행합니다).
I
는
std::size_t
prvalue이며,
<
I
>
는 항상 템플릿 매개변수 목록으로 해석됩니다.
변수는 storage duration 이 e 와 동일합니다.
구조적 바인딩은 해당 변수에 바인딩된 객체를 참조하는 lvalue의 이름이 됩니다.
참조된 타입 은 I 번째 구조적 바인딩에 대해 std:: tuple_element < I, E > :: type 입니다.
float x{}; char y{}; int z{}; std::tuple<float&, char&&, int> tpl(x, std::move(y), z); const auto& [a, b, c] = tpl; // Tpl = const std::tuple<float&, char&&, int> 사용 // a는 x를 참조하는 구조화된 바인딩을 나타냄 (get<0>(tpl)로부터 초기화됨) // decltype(a)는 std::tuple_element<0, Tpl>::type, 즉 float& // b는 y를 참조하는 구조화된 바인딩을 나타냄 (get<1>(tpl)로부터 초기화됨) // decltype(b)는 std::tuple_element<1, Tpl>::type, 즉 char&& // c는 tpl의 세 번째 구성 요소인 get<2>(tpl)를 참조하는 구조화된 바인딩을 나타냄 // decltype(c)는 std::tuple_element<2, Tpl>::type, 즉 const int
Case 3: 데이터 멤버에 바인딩
E
의 모든 비정적 데이터 멤버는
E
의 직접 멤버이거나
E
의 동일한 기본 클래스에 속해야 하며,
e.
name
로 명명될 때 구조적 바인딩 컨텍스트에서 올바르게 형성되어야 합니다.
E
는 익명 공용체 멤버를 가질 수 없습니다.
E
의 구조적 바인딩 크기는 비정적 데이터 멤버의 수와 동일합니다.
sb-identifier-list
내의 각 구조적 바인딩은 선언 순서대로
e
의 다음 멤버를 참조하는 lvalue의 이름이 됩니다(비트 필드는 지원됨). 이 lvalue의 타입은
e.
mI
의 타입과 동일하며, 여기서
mI
는
I
번째 멤버를 나타냅니다.
참조된 타입
은
I
번째 구조적 바인딩의 타입이 참조 타입이 아닌 경우
e.
mI
의 타입이며, 그렇지 않은 경우
mI
의 선언된 타입입니다.
#include <iostream> struct S { mutable int x1 : 2; volatile double y1; }; S f() { return S{1, 2.3}; } int main() { const auto [x, y] = f(); // x는 2비트 비트 필드를 식별하는 int lvalue // y는 const volatile double lvalue std::cout << x << ' ' << y << '\n'; // 1 2.3 x = -2; // OK // y = -2.; // 오류: y는 const 한정됨 std::cout << x << ' ' << y << '\n'; // -2 2.3 }
초기화 순서
sb-identifier-list 에서 I 번째 구조적 바인딩에 명명된 객체 또는 참조를 valI 라고 하자:
- e 의 초기화는 모든 valI 의 초기화보다 먼저 순서가 정해집니다 .
- 각 valI 의 초기화는 I 가 J 보다 작은 모든 valJ 의 초기화보다 먼저 순서가 정해집니다.
참고 사항
|
구조화된 바인딩은 제약 조건 을 가질 수 없습니다: template<class T> concept C = true; C auto [x, y] = std::pair{1, 2}; // error: constrained |
(C++20부터) |
멤버
get
에 대한 조회는 일반적으로 접근성을 무시하며, 상수 템플릿 매개변수의 정확한 타입도 무시합니다. private
template
<
char
*
>
void
get
(
)
;
멤버가 존재하면, 해당 멤버가 잘못된 형태임에도 불구하고 멤버 해석이 사용됩니다.
선언의 앞부분은 도입된 구조적 바인딩이 아닌 숨겨진 변수 e 에 적용됩니다:
튜플-유사 해석은 다음 조건에서 항상 사용됩니다:
std::
tuple_size
<
E
>
가 멤버
value
를 갖는 완전한 타입인 경우, 심지어 그것이 프로그램을 부적합하게 만들더라도:
struct A { int x; }; namespace std { template<> struct tuple_size<::A> { void value(); }; } auto [x] = A{}; // 오류; "데이터 멤버" 해석은 고려되지 않습니다.
임시 객체에 대한 참조 바인딩의 일반적인 규칙들(수명 연장 포함)은 ref-qualifier 가 존재하고 해당 expression 가 prvalue인 경우 적용됩니다. 이러한 경우 숨겨진 변수 e 는 prvalue 표현식에서 materialized 된 임시 변수에 바인딩되는 참조이며, 그 수명을 연장합니다. 일반적으로, e 가 non-const lvalue reference인 경우 바인딩은 실패합니다:
int a = 1; const auto& [x] = std::make_tuple(a); // 정상, 댕글링 참조가 아님 auto& [y] = std::make_tuple(a); // 오류, rvalue std::tuple에 auto&를 바인딩할 수 없음 auto&& [z] = std::make_tuple(a); // 역시 정상
decltype ( x ) , 여기서 x 가 구조화된 바인딩을 나타내는 경우, 해당 구조화된 바인딩의 참조 타입 을 명시합니다. 튜플-유사(tuple-like) 경우에는, 이는 std::tuple_element 가 반환하는 타입으로, 이 경우 숨겨진 참조가 항상 도입되더라도 참조 타입이 아닐 수 있습니다. 이는 바인딩 자체의 참조성이 단순한 구현 세부사항인, std::tuple_element 가 반환하는 타입을 가진 비정적 데이터 멤버들로 구성된 구조체에 바인딩하는 동작을 효과적으로 모방합니다.
std::tuple<int, int&> f(); auto [x, y] = f(); // decltype(x)는 int입니다 // decltype(y)는 int&입니다 const auto [z, w] = f(); // decltype(z)는 const int입니다 // decltype(w)는 int&입니다
|
구조화된 바인딩은 람다 표현식 으로 캡처할 수 없습니다: #include <cassert> int main() { struct S { int p{6}, q{7}; }; const auto& [b, d] = S{}; auto l = [b, d] { return b * d; }; // valid since C++20 assert(l() == 42); } |
(C++20 이전) |
|
구조화된 바인딩의 크기는 sb-identifier-list 가 빈 구조화된 바인딩 팩만 도입할 수 있는 식별자를 정확히 하나 포함하는 한 0 이 허용됩니다. auto return_empty() -> std::tuple<>; template <class> void test_empty() { auto [] = return_empty(); // error auto [...args] = return_empty(); // OK, args is an empty pack auto [one, ...rest] = return_empty(); // error, structured binding size is too small } |
(C++26부터) |
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_structured_bindings
|
201606L
|
(C++17) | 구조화된 바인딩 |
202403L
|
(C++26) | 속성을 갖는 구조화된 바인딩 | |
202406L
|
(C++26) | 조건문으로서의 구조화된 바인딩 선언 | |
202411L
|
(C++26) | 팩을 도입할 수 있는 구조화된 바인딩 |
키워드
예제
#include <iomanip> #include <iostream> #include <set> #include <string> int main() { std::set<std::string> myset{"hello"}; for (int i{2}; i; --i) { if (auto [iter, success] = myset.insert("Hello"); success) std::cout << "Insert is successful. The value is " << std::quoted(*iter) << ".\n"; else std::cout << "The value " << std::quoted(*iter) << " already exists in the set.\n"; { struct BitFields { // C++20: 비트 필드에 대한 기본 멤버 초기화 int b : 4 {1}, d : 4 {2}, p : 4 {3}, q : 4 {4}; }; { const auto [b, d, p, q] = BitFields{}; std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n'; } { const auto [b, d, p, q] = []{ return BitFields{4, 3, 2, 1}; }(); std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n'; } { BitFields s; auto& [b, d, p, q] = s; std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n'; b = 4, d = 3, p = 2, q = 1; std::cout << s.b << ' ' << s.d << ' ' << s.p << ' ' << s.q << '\n'; } }
출력:
Insert is successful. The value is "Hello". The value "Hello" already exists in the set. 1 2 3 4 4 3 2 1 1 2 3 4 4 3 2 1
결함 보고서
다음 동작 변경 결함 보고서는 이전에 게시된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 2285 | C++17 | expression 이 identifier-list 의 이름들을 참조할 수 있었음 |
이 경우 선언이
ill-formed임 |
| CWG 2312 | C++17 | case 3에서 mutable 의 의미가 소실됨 | 그 의미가 여전히 유지됨 |
| CWG 2313 | C++17 | case 2에서 구조화 바인딩 변수들을 재선언할 수 있었음 | 재선언할 수 없음 |
| CWG 2339 | C++17 | case 2에서 I 의 정의가 누락됨 | 정의를 추가함 |
|
CWG 2341
( P1091R3 ) |
C++17 |
구조화 바인딩을
정적 저장 기간으로 선언할 수 없었음 |
허용됨 |
| CWG 2386 | C++17 |
"tuple-like" 바인딩 프로토콜이
std:: tuple_size < E > 가 완전한 타입일 때마다 사용됨 |
std::
tuple_size
<
E
>
가 멤버
value
를 가질 때만 사용됨
|
| CWG 2506 | C++17 |
expression
이 cv-qualified 배열 타입인 경우,
cv-qualification이
E
로 이월됨
|
해당 cv-qualification을 버림 |
| CWG 2635 | C++20 | 구조화 바인딩에 제약을 줄 수 있었음 | 금지됨 |
| CWG 2867 | C++17 | 초기화 순서가 불명확했음 | 명확하게 함 |
| P0961R1 | C++17 |
case 2에서 어떤 종류의
get
이라도
조회되면 멤버
get
이 사용됨
|
상수 매개변수를 가진 함수 템플릿을
조회할 때만 사용됨 |
| P0969R0 | C++17 | case 3에서 멤버들이 public이어야 했음 |
선언 문맥에서 접근 가능하기만 하면 됨
|
참고문헌
- C++23 표준 (ISO/IEC 14882:2024):
-
- 9.6 구조적 바인딩 선언 [dcl.struct.bind] (p: 228-229)
- C++20 표준(ISO/IEC 14882:2020):
-
- 9.6 구조화된 바인딩 선언 [dcl.struct.bind] (p: 219-220)
- C++17 표준 (ISO/IEC 14882:2017):
-
- 11.5 구조화된 바인딩 선언 [dcl.struct.bind] (p: 219-220)
참고 항목
|
(C++11)
|
lvalue 참조의
tuple
을 생성하거나 tuple을 개별 객체로 언패킹합니다
(함수 템플릿) |