---
tags: 自學
---
# Structured binding declaration
###### tags: `C++17` `cppreference`
Structured binding 的宣告有以下三種
- attr(optional) cv-auto ref-qualifier(optional) \[identifier-list\] = expression;
- attr(optional) cv-auto ref-qualifier(optional) \[identifier-list\]{expression};
- attr(optional) cv-auto ref-qualifier(optional) \[identifier-list\](expression);
Description:
- attr: 標籤的序列,詳見[cppreference](https://en.cppreference.com/w/cpp/language/attributes)
- cv-auto: `cv`代表的是`cv-qualified type`,意思是`const`或者`volatile`,這裡的`auto`可能是`const`或者`volatile`。(從C++20開始,這裡的`auto`可能是`const`, `static`或者`thread_local`,不再包含`volatile`)
- ref-qulifier: `&`或者`&&`
- identifier-list: 用逗號分隔的變數名稱
- expression: 在最上層沒有逗號的表達式,而且有array或者不是`union`的資料型態。如果這個 expression 是指向任意一個 identifier-list 中的identifier,那這個宣告是 **ill-formed**的。
Structured binding declaration 在 identifier-list 中宣告了所有的變identifier,並且將每個 identifier 綁定到 expression 中的元素或子物件中。這樣引入的綁定稱為結構化綁定。
以下用$E$表示 expression(e) 的資料型態,因此,$E$與`std::remove_reference_t<decltype((e))>`相同。
Structured binding declaration 根據$E$的型態,提供下列三種不同的綁定
- Case 1: $E$ 是 array type
- Case 2: $E$ 不是`union`型態,並且`std::tuple_size<E>`是一個完整的型態
- Case 3: $E$ 不是`union`型態,並且`std::tuple_size<E>`不是一個完整的型態
## 使用情境
### Case 1 Array
每個 identifier-list 中的identifier 作為`lvalue`對應到 array 中的元素,兩邊的元素個數應該一致。
每個identifier 的引用型態等於array元素的型態,要注意若$E$有`cv`,則變數也會有這些`cv`。
```cpp=
int a[2] = {1, 2};
auto [x, y] = a; // x = a[0], y = a[1]
auto& [xr, yr] = a; // xr refer to a[0] and yr refer to a[1]
x = 3; // a = {1, 2}
xr = 3; // a = {3, 2}
```
### Case 2 Tuple-like Type
`std::tuple_size<E>::value`必須是**well-formed**的不變整數,identifier 的數量應該與`std::tuple_size<E>::value`相等。
對於每個型態是`reference to std::tuple_element<i, E>::type`的identifier,若他的 initializer 是`lvalue`,則為`lvalue reference`,否則為`rvalue reference`。i-th variable的 initializer為:
- `e.get<i>()`: 找到至少一個function template宣告,且第一個 template parameter不是型態參數的 function template。
- `get<i>(e)`: Otherwise。get只會用[ADL](https://en.cppreference.com/w/cpp/language/adl)查找。
在這些 initializer中,若$e$是`lvalue reference`(這只會在 ref-qualifier是&或者是&&且是lvalue時發生)則`e`是`lvalue`,除此之外都是[xvalue](https://en.cppreference.com/w/cpp/language/value_category#xvalue),`i`是一個 `std::size_t` [prvalue](https://en.cppreference.com/w/cpp/language/value_category#prvalue),而`<i>`則都是在表示一個 template parameter list。
每個變數都跟$e$有相同的[storage duration](https://en.cppreference.com/w/cpp/language/storage_duration)。每個identifier 變成左值的名稱,該左值引用綁定到變數的物件。第i個identifier的引用型態是`std::tuple_element<i, E>::type`
```cpp=
float x{};
char y{};
int z{};
std::tuple<float&, char&&, int> tpl(x, move(y), z);
// tpl = {0, ' ', 0}
const auto& [a, b, c] = tpl; // a = 0, b = ' ', c = 0
a = 10; // tpl = {10, ' ', 0}, x = 10, y = ' ', z = 0
b = 'a'; // tpl = {10, 'a', 0}, x = 10, y = 'a', z = 0
c = 20; // error; 宣告的是const,因此c不能重新附值
// 除非tuple中,他也是refer到其他變數
/*
using Tpl = const std::tuple<float&, char&&, int>;
a names a structured binding that refers to x (initialized from get<0>(tpl))
decltype(a) is std::tuple_element<0, Tpl>::type, i.e. float&
b names a structured binding that refers to y (initialized from get<1>(tpl))
decltype(b) is std::tuple_element<1, Tpl>::type, i.e. char&&
c names a structured binding that refers to the third component of tpl, get<2>(tpl)
decltype(c) is std::tuple_element<2, Tpl>::type, i.e. const int
*/
```
### Case 3 Data Members
identifier 的數量必須與 non-static data members的數量相同,且$E$不能是`union`型別。每個identifier 就是物件成員的別稱。
```cpp=
struct S{
mutable int x1:2;
double y1;
};
S f(){return S{1, 2.3};}
int main(){
const auto [x, y] = f(); // x = 1, y = 2.3
x = -1; // x = -1, y = 2.3
// 由於在結構S中的x1為mutable
// 因此即便x被宣告為const,仍然可以改變
y = -2; // error; 宣告的是const,因此不能重新附值
}
/* Another example */
struct S2{
int &x2;
double &y2;
};
int a = 1;
double b = 2.3;
S2 g(){return S2{a, b};}
int main(){
const auto [x, y] = g();
// x = 1, y = 2.3
// x refer to a, y refer to b
x = -1; // a = -1, b = 2.3
y = -2.3; // a = -1, b = -2.3
}
```
## 參考資料
- [cppreference](https://en.cppreference.com/w/cpp/language/structured_binding) Structured binding declaration (since C++17)