结构化绑定声明 (C++17 起)

来自cppreference.com
< cpp‎ | language

绑定指定名称到初始化器的子对象或元素。

类似引用,结构化绑定是既存对象的别名。不同于引用的是,结构化绑定的类型不必为引用类型。

attr(可选) cv-auto ref-operator(可选) [ identifier-list ] = expression ; (1)
attr(可选) cv-auto ref-operator(可选) [ identifier-list ] { expression } ; (2)
attr(可选) cv-auto ref-operator(可选) [ identifier-list ] ( expression ) ; (3)
attr - 任何数量的属性序列
cv-auto - 可有 cv 限定的类型指定符 auto
ref-operator - &&& 之一
identifier-list - 此声明所引入的逗号分隔标识符(结构化绑定)的列表
expression - 在顶层无逗号运算符的表达式(文法上为赋值表达式),并拥有数组或非联合类之一的类型。若 expression 指代任何来自 identifier-list 的名称,则声明为病式。

结构化绑定声明在外围作用域中引入 identifier-list 中的所有标识符,并将它们绑定到 expression 所指代的对象的子对象或元素。以此方式引入的绑定被称作结构化绑定

结构化绑定声明首先引入一个唯一命名的变量(此处以 e 指代),以如下方式保有初始化器的值:

  • expression 拥有数组类型 A 且不存在 ref-operator ,则 e 拥有类型 cv A ,其中 cvcv-auto 序列中的 cv 限定符,且 e 的每个引用从 expression 的对应元素复制(对于 (1) )或直接(对于 (2,3) )初始化。
  • 否则 e 如同于声明中以其名取代 [ identifier-list ] 一般定义。

我们用 E 指代表达式 e 的类型。(换言之, E 等价于 std::remove_reference_t<decltype((e))> )。

然后,结构化绑定以三种可能方式之一进行绑定,取决于 E

  • 情况 1 :若 E 是数组类型,则绑定名称到数组元素。
  • 情况 2 :若 E 是非联合类类型且 std::tuple_size<E> 是完整类型,则使用“类 tuple ”绑定协议。
  • 情况 3 :若 E 是非联合类类型但 std::tuple_size<E> 不是完整类型,则绑定名称到 E 的可访问数据成员。

三种情况的每一种都更详细地描述于下。

每个结构化绑定拥有一个被引用类型,以下方描述定义。此类型是在应用到无括号的结构化绑定时 decltype 所返回的类型。

目录

[编辑] 情况 1 :绑定数组

每个 identifier-list 中的标识符成为指代数组对应元素的左值。标识符的数量必须等于数组的元素数量。

每个标识符的被引用类型都是数组元素类型。注意数组类型 E 为 cv 限定,则其元素亦然。

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]

[编辑] 情况 2 :绑定类 tuple 类型

表达式 std::tuple_size<E>::value 必须是良式的整数常量表达式,且标识符的数量必须等于 std::tuple_size<E>::value

对于每个标识符,引入一个类型为“到 std::tuple_element<i, E>::type 的引用”的变量:若对应初始化器是左值,则为左值引用,否则为右值引用。第 i 个变量的初始化器是

  • e.get<i>() ,若 get 的查找在 E 作用域中的按类成员访问查找中,至少找到一个声明,而且是首个模板形参为非类型形参的函数模板
  • 否则为 get<i>(e) ,其中 get 只按照实参依赖查找查找,忽略非 ADL 查找。

这些初始化器表达式中,若实体 e 的类型为左值引用则 e 是左值(这仅若 ref-operator& 或它是 && 且初始化器为左值才发生),否则为亡值(这等效地进行一种完美转发), istd::size_t 纯右值,而且始终转译 <i> 为模板形参列表。

然后标识符变成指代绑定到上述变量的对象的左值。

第 i 个标识符的被引用类型std::tuple_element<i, E>::type

float x{};
char  y{};
int   z{};
 
const auto& [a, b, c] = std::tuple<float&, char&&, int>(x, std::move(y), z);
// a 指名指代 x 的结构化绑定; decltype(a) 为 float&
// b 指名指代 y 的结构化绑定; decltype(b) 为 char&&
// c 指名指代 tpl 的第 3 元素的结构化绑定; decltype(c) 为 const int

[编辑] 情况 3 :绑定到数据成员

E 的每个非静态数据成员必须是 E 的,或 E 同一基类的直接成员,必须在指名为 e.name 时于结构化绑定的语境中为良式。 E 不能有匿名联合体成员。标识符的数量必须等于非静态数据成员的数量。

每个 identifier-list 中的标识符,按声明顺序依次成为指代 e 成员的左值的名称(支持位域);左值的类型是 cv T_i ,其中 cvE 的 cv 限定符且 T_i 是第 i 个成员的声明类型。

第 i 个标识符的被引用类型cv T_i

struct S {
    int x1 : 2;
    volatile double y1;
};
S f();
 
const auto [x, y] = f(); // x 是标识 2 位位域的 const int 左值
                         // y 是 const volatile double 左值

[编辑] 注意

成员 get 的查找照常忽略可访问性,亦忽略非类型模板形参的准确类型。私有的 template<char*> void get(); 将导致使用成员转译,即使它为病式。

声明前附于 [ 的部分应用到隐藏变量 e ,而非引入的标识符。

int a = 1, b = 2;
const auto& [x, y] = std::tie(a, b); // x 与 y 类型为 int&
auto [z, w] = std::tie(a, b);        // z 与 w 类型仍为 int&
assert(&z == &a);                    // 通过

std::tuple_size<E> 是完整类型则始终使用类 tuple 转译,即使导致程序为病式:

struct A { int x; };
namespace std {
    template<> struct tuple_size<::A> {};
}
 
auto [x] = A{}; // 错误;不考虑“数据成员”转译。

若存在 ref-operatorexpression 为纯右值,则应用引用绑定到临时量的通常规则(包含生存期延续)。该情况下隐藏变量 e 是绑定到从纯右值表达式实质化的临时变量,并延长其生存期。如往常情况,若 e 是非 const 左值引用,则绑定失败:

int a = 1;
const auto& [x] = std::make_tuple(a); // OK ,非悬垂
auto&       [y] = std::make_tuple(a); // 错误,不能绑定 auto& 到右值 std::tuple
auto&&      [z] = std::make_tuple(a); // 亦 OK

decltype(x) 指名结构化绑定的被引用类型,其中 x 指代结构化绑定。在类 tuple 情况下,这是 std::tuple_element 所返回的类型,它可能不是引用,即使在此情况结构化绑定自身实际上始终表现类似引用。这等效地模拟绑定到其非静态数据成员拥有 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 <set>
#include <string>
#include <iomanip>
#include <iostream>
 
int main() {
    std::set<std::string> myset;
    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";
}

输出:

insert is successful. The value is "Hello"

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

DR 应用于 出版时的行为 正确行为
P0961R1 C++17 类 tuple 情况中,若查找找到任何类型的 get 则使用成员 get 仅若查找找到拥有非类型模板形参的函数模板才使用
P0969R0 C++17 绑定到成员情况中,要求成员为公开 仅要求在声明的语境中可访问

[编辑] 参阅

创建左值引用的一个 tuple ,或解包 tuple 为独立对象
(函数模板) [编辑]