第 20.6 節

structured binding

0瀏覽次數0訪問次數--跳出率--平均停留

What problem does this section solve?

When a function needs to return multiple values (like a success flag plus data), or when you need to access both the key and value while iterating through a map, prior to C++17 you had to use workarounds like std::pair / std::tuple with std::get<>(), or .first / .second—making the code verbose and harder to read.

Structured bindings allow you to decompose composite types (pair, tuple, structs) into individual variables in one line of code, making the code much clearer.

What is this feature?

Structured Bindings, introduced in C++17, allow you to "bind" elements from a struct, pair, tuple, or array to individual variable names.

C++ standard version

C++17。编译本节完整示例时,需要启用 C++17 或更高标准,例如:

g++ example.cpp -std=c++17

Required header files

No additional header files are needed. Structured binding is a language feature. For use with tuples, you need <tuple>, and for use with pairs, you need <utility>.

从 C++17 前的写法到结构化绑定

C++17 之前没有统一的“解包”语法。面对不同的复合类型,需要使用各自的访问方式:

|type|C++17 之前的访问方式|C++17 结构化绑定|

|:---|:---|:---| |std::pair|.first.second|auto [first, second] = pair;| |std::tuple|std::get<0>()std::get<1>() 等|auto [a, b, ...] = tuple;|

|structure|.成员名|auto [a, b, ...] = object;| |array|[0][1] 等下标|auto [a, b, ...] = array;|

std::pair 为例,C++17 之前需要分别访问 firstsecond

std::pair<int, bool> result = {5, true};

// C++17 之前:通过 pair 固定的成员名 first 和 second 取出两个值。
int value = result.first;
bool ok = result.second;

C++17 开始,可以使用结构化绑定一次取出两个值,并根据含义为它们命名:

std::pair<int, bool> result = {5, true};

// C++17:value 对应 result.first,ok 对应 result.second。
auto [value, ok] = result;

结构化绑定没有改变 pairtuple、结构体或数组本身,只是提供了一套统一、简洁的取值语法。方括号中的变量按元素或成员的原有顺序一一对应。

Basic Syntax

// 以下三种写法都是 C++17 开始支持的结构化绑定。
auto [变量1, 变量2, ...] = 表达式;        // 值绑定:通常会得到各元素的副本。
auto& [变量1, 变量2, ...] = 表达式;       // 引用绑定:可以通过变量修改原对象。
const auto& [变量1, 变量2, ...] = 表达式;  // 只读引用绑定:不拷贝,也不允许修改。

Common Usage

下表中的写法都需要启用 C++17 或更高版本:

UsageExplanation
auto [x, y] = pairUnpack pair
auto [a, b, c] = std::tuple<...>(...)Unpack tuples
auto [x, y, z] = struct_objUnpack struct (in member order)
for (auto& [k, v] : map)Traversing a map while unpacking key/value pairs
auto [it, ok] = map.insert(...)Unpack the pair<iterator, bool> returned by insert.

Example code

示例 1:函数返回 pair——从 C++17 前的成员访问到结构化绑定

#include <iostream>
#include <utility>  // for std::pair

// 返回两个值的函数
std::pair<int, bool> divide(int a, int b)
{
    if (b == 0)
    {
        return {0, false};  // 除数为 0,返回失败
    }
    return {a / b, true};   // 返回结果和成功标志
}

int main()
{
    // C++17 之前:先接收完整的 pair 对象。
    const std::pair<int, bool> old_result = divide(10, 2);

    // pair 的 first 是第一个值,这里代表除法结果。
    const int old_value = old_result.first;

    // pair 的 second 是第二个值,这里代表操作是否成功。
    const bool old_ok = old_result.second;

    if (old_ok)
    {
        std::cout << "before C++17: 10 / 2 = " << old_value << "\n";
    }

    // C++17:结构化绑定把返回的 pair 按顺序拆成 value 和 ok。
    // value 对应 pair.first,ok 对应 pair.second。
    const auto [value, ok] = divide(10, 2);
    if (ok)
    {
        std::cout << "C++17: 10 / 2 = " << value << "\n";
    }

    // C++17:同样的方法也可以接收失败结果。
    const auto [failed_value, failed_ok] = divide(10, 0);

    // failed_ok 为 false,说明 failed_value 不是有效的除法结果。
    if (!failed_ok)
    {
        std::cout << "10 / 0: division failed\n";
    }

    return 0;
}

Results

before C++17: 10 / 2 = 5
C++17: 10 / 2 = 5
10 / 0: division failed

示例 2:tuple 和数组——从逐项访问到 C++17 结构化绑定

#include <iostream>
#include <string>
#include <tuple>

// 返回多个值
std::tuple<std::string, int, double> get_student_info()
{
    return {"Alice", 20, 3.8};  // 姓名、年龄、GPA
}

int main()
{
    // C++17 之前:先保存完整的 tuple。
    const auto old_info = get_student_info();

    // std::get<下标>() 按位置访问 tuple,编号从 0 开始。
    const std::string& old_name = std::get<0>(old_info);
    const int old_age = std::get<1>(old_info);
    const double old_gpa = std::get<2>(old_info);

    std::cout << "before C++17 tuple: "
              << old_name << ", " << old_age << ", " << old_gpa << "\n";

    // C++17:结构化绑定按 tuple 中元素的顺序声明三个变量。
    // const auto& 表示只读引用,可以避免复制 tuple 中的 string。
    const auto& [name, age, gpa] = old_info;
    std::cout << "C++17 tuple: "
              << name << ", " << age << ", " << gpa << "\n";

    int numbers[] = {10, 20, 30};

    // C++17 之前:使用下标逐个访问数组元素。
    std::cout << "before C++17 array: "
              << numbers[0] << ", " << numbers[1] << ", " << numbers[2] << "\n";

    // C++17:数组有三个元素,所以左侧也必须声明三个名字。
    // 这里没有写 &,因此 a、b、c 是三个元素的副本。
    const auto [a, b, c] = numbers;
    std::cout << "C++17 array: " << a << ", " << b << ", " << c << "\n";

    return 0;
}

Results

before C++17 tuple: Alice, 20, 3.8
C++17 tuple: Alice, 20, 3.8
before C++17 array: 10, 20, 30
C++17 array: 10, 20, 30

示例 3:结构体——从成员访问到 C++17 结构化绑定

#include <iostream>
#include <string>

struct Point
{
    double x;
    double y;
    double z;
};

Point midpoint(const Point& p1, const Point& p2)
{
    return {
        (p1.x + p2.x) / 2.0,
        (p1.y + p2.y) / 2.0,
        (p1.z + p2.z) / 2.0
    };
}

int main()
{
    Point p1{1.0, 2.0, 3.0};
    Point p2{5.0, 6.0, 7.0};

    // C++17 之前:先接收完整的结构体,再通过成员名访问数据。
    const Point old_middle = midpoint(p1, p2);
    std::cout << "before C++17 midpoint: ("
              << old_middle.x << ", "
              << old_middle.y << ", "
              << old_middle.z << ")\n";

    // C++17:结构化绑定按成员的声明顺序解包结构体。
    // x 对应 Point::x,y 对应 Point::y,z 对应 Point::z。
    const auto [x, y, z] = midpoint(p1, p2);
    std::cout << "C++17 midpoint: (" << x << ", " << y << ", " << z << ")\n";

    // C++17:也可以直接解包临时创建的结构体对象。
    const auto [a, b, c] = Point{10.0, 20.0, 30.0};
    std::cout << "C++17 temporary point: ("
              << a << ", " << b << ", " << c << ")\n";

    return 0;
}

Results

before C++17 midpoint: (3, 4, 5)
C++17 midpoint: (3, 4, 5)
C++17 temporary point: (10, 20, 30)

示例 4:map——对比 C++17 前后的插入结果和遍历写法

#include <iostream>
#include <map>
#include <string>

int main()
{
    std::map<std::string, int> scores;

    // map::insert 返回一个 pair<iterator, bool>:
    // first 是指向相应元素的迭代器,second 表示是否插入成功。

    // C++17 之前:先接收完整的 pair,再分别读取 first 和 second。
    const auto old_insert_result = scores.insert({"Alice", 85});
    const auto old_it = old_insert_result.first;
    const bool old_inserted = old_insert_result.second;

    if (old_inserted)
    {
        // 迭代器的 second 是 map 元素的 value,这里是 Alice 的分数。
        std::cout << "before C++17 insert: Alice inserted, score: "
                  << old_it->second << "\n";
    }

    // C++17:直接把 insert 返回的 pair 解包为迭代器和成功标志。
    // 由于 Alice 已经存在,这次重复插入会失败。
    const auto [it, inserted] = scores.insert({"Alice", 100});
    if (!inserted)
    {
        std::cout << "C++17 insert: Alice already exists, score: "
                  << it->second << "\n";
    }

    // 添加另外两个元素,供下面演示遍历使用。
    scores.insert({"Bob", 92});
    scores.insert({"Charlie", 78});

    // C++17 之前:范围 for 每次取得一个 pair。
    // pair.first 是 map 的 key,pair.second 是 map 的 value。
    std::cout << "\nbefore C++17 traversal:\n";
    for (const auto& pair : scores)
    {
        std::cout << "  " << pair.first << ": " << pair.second << "\n";
    }

    // C++17:在范围 for 中使用结构化绑定,直接命名 key 和 value。
    // const auto& 表示只读引用,既不复制元素,也不会修改 map。
    std::cout << "C++17 traversal:\n";
    for (const auto& [name, score] : scores)
    {
        std::cout << "  " << name << ": " << score << "\n";
    }

    return 0;
}

Results

before C++17 insert: Alice inserted, score: 85
C++17 insert: Alice already exists, score: 85

before C++17 traversal:
  Alice: 85
  Bob: 92
  Charlie: 78
C++17 traversal:
  Alice: 85
  Bob: 92
  Charlie: 78

runtime results

See the "running results" for each example above.

Key syntax explanation in the example

|Here is the translation of the provided Simplified Chinese Markdown fragment into natural American English, following all specified rules.


ExampleDiscusses whatNewly emerged syntaxWhy write it this wayPrecautions
Example 1pair.first/second 改为解包 pairauto [value, ok] = pairC++17 可以一次声明两个有意义的变量名变量名可以自定义,但对应顺序不能改变

|Example 2|从 std::get<N>()、数组下标改为统一解包|auto [a,b,c] = tupleauto [a,b,c] = array|C++17 对 tuple 和数组采用相似语法|变量个数必须和 tuple、数组的元素个数一致|

|Example 3|从 .成员名 改为解包结构体|auto [x,y,z] = struct_obj|C++17 按结构体成员声明顺序绑定|可绑定的成员需要满足访问条件,且顺序由结构体定义决定|

|Example 4|对比 C++17 前后的 map 写法|auto [it, ok] = m.insert(...)[name, score]|返回值和 map 元素都能直接取出并命名|insert returns pair<iterator, bool>|

The difference between auto [a, b] and auto& [a, b]

下面讨论的 auto [a, b]auto& [a, b] 都是 C++17 结构化绑定语法。结构化绑定不写 & 时通常会产生副本;遍历大对象或想修改原数据时,需要考虑使用引用。

C++17 之前没有结构化绑定。修改 map 的 value 时,通常取得整个 pair 的引用,再修改它的 second

// C++17 之前:pair.second 是 map 元素的 value。
for (auto& pair : scores)
{
    pair.second += 10;
}

示例 5:C++17 结构化绑定中,修改 map 的 value 必须使用引用

#include <iostream>
#include <map>
#include <string>

int main()
{
    std::map<std::string, int> scores = {
        {"Alice", 85},
        {"Bob", 92}
    };

    // C++17:没有写 &,name 和 score 不引用 map 中的原元素。
    // 每轮循环得到的是当前 key 和 value 的副本。
    for (auto [name, score] : scores)
    {
        // 这里只修改局部变量 score,scores 中的原分数不会变化。
        score += 10;
    }

    std::cout << "after auto copy:\n";

    // C++17:const auto& 不复制 map 元素,适合只读输出。
    for (const auto& [name, score] : scores)
    {
        std::cout << name << ": " << score << "\n";
    }

    // C++17:auto& 让 name 和 score 引用 map 中的原元素。
    for (auto& [name, score] : scores)
    {
        // score 是 value 的引用,因此这次会修改原 map 中的分数。
        score += 10;
    }

    std::cout << "after auto& reference:\n";

    // 修改完成后再次只读遍历,确认新分数已经保存在 map 中。
    for (const auto& [name, score] : scores)
    {
        std::cout << name << ": " << score << "\n";
    }

    return 0;
}

Results

after auto copy:
Alice: 85
Bob: 92
after auto& reference:
Alice: 95
Bob: 102

注意:map 的 key 仍然不能改。即使使用 C++17 的 auto& [name, score]name 也是只读的,因为 map 必须保证 key 不变,才能维持内部的排序规则。

Common Errors

Error 1: Variable count mismatch

// C++17:右侧 tuple 有三个元素,左侧却只有两个变量,无法一一对应。
auto [x, y] = std::make_tuple(1, 2, 3);  // ❌ 编译错误

Correct approach: The number of variables must match the number of elements in the composite type.

Error 2: Unpacking a struct with non-public members

struct A { private: int x; public: int y; };
A obj;

// C++17:此处不能访问私有成员 x,因此不能这样进行结构化绑定。
auto [x, y] = obj;  // ❌ 编译错误

正确做法:在使用结构化绑定的位置,所有被绑定的成员都必须可以访问。对初学阶段常见的普通结构体来说,最直观的做法是让这些成员保持 public

Error 3: Using auto instead of auto& causes changes to be ineffective

// C++17:没有写 &,k 和 v 是从 map 元素得到的副本。
auto [k, v] = *map.begin();

// 这里只修改局部变量 v,不会修改 map 中的 value。
v = 100;

The correct approach is to use auto& [k, v] = ... to modify the original value.

使用建议

  • 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
  • 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
  • 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
  • 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
  • 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。

如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。

  1. When a function returns multiple values, prefer std::tuple/std::pair + structured bindings over output parameters.
  2. C++17 中只读遍历 map 时优先用 const auto& [key, value],避免拷贝,也能清楚表达 key 和 value 的含义。
  1. 解包 insert 返回值:C++17 的 auto [it, inserted] = map.insert(...) 比继续使用 .first.second 更直观。
  1. 需要修改原数据时使用 auto&;只读且不想拷贝时使用 const auto&
  1. 维护 C++17 以前的项目时继续使用原有访问方式:pair 用 .first/.second,tuple 用 std::get<N>()

Summary

  • C++17 之前需要通过 .first/.secondstd::get<N>()、成员名或数组下标分别取值。
  • C++17 结构化绑定可以把 pair / tuple / 结构体 / 数组分解成独立变量。
  • C++17 基本语法是 auto [var1, var2, ...] = expression
  • When iterating through a map, [key, value] is best practice (C++17).
  • Using the [iterator, bool] to unpack the return value is quite clear.
音乐页