这段代码逐层迭代二维 std::vector,意思是:

#include <vector>
#include <iostream>
int main() {
    std::vector<std::vector<int>> vec = {{1, 0}, {0, 1}};
    // 创建一个二维向量,相当于:
    // vec[0] = {1, 0}
    // vec[1] = {0, 1}
    for (std::vector<std::vector<int>>::iterator i = vec.begin(); i != vec.end(); ++i) {
        // 外层循环:i 是一个迭代器,指向 vec 中的每一行(std::vector<int>)
        for (std::vector<int>::const_iterator j = i->begin(); j != i->end(); ++j) {
            // 内层循环:j 是一个迭代器,指向当前行里的每个元素(int)
            /* do something */
            // 在这里对 *j 进行处理
            std::cout << *j << " ";
        }
    }
}

解释

  • 外层循环:遍历二维向量的每一行(即 std::vector<int>)。
  • 内层循环:遍历当前行中的每一个 int 元素。
  • i非常量迭代器,所以外层可以修改行(例如增加或删除元素)。
  • j常量迭代器,表示内层只读取数据,不会修改它。
    如果你想用现代 C++ 的写法,可以改成更简洁的范围 for 循环
#include <vector>
#include <iostream>
int main() {
    std::vector<std::vector<int>> vec = {{1, 0}, {0, 1}};
    // 创建一个二维向量,相当于:
    // vec[0] = {1, 0}
    // vec[1] = {0, 1}
    for (auto& row : vec) {
        for (const auto& value : row) {
            // 内层循环:j 是一个迭代器,指向当前行里的每个元素(int)
            /* do something */
            // 在这里对 *j 进行处理
            std::cout << value << " ";
        }
    }
}

这种写法更易读,而且不用写长长的迭代器类型。
如果你愿意,我可以帮你画一个二维迭代器指针移动示意图,让这段代码更直观。这样能更快理解。

这段代码的作用是:自定义一个比较器 sorter_type,然后用它来对 vec(一个 std::vector<std::vector<int>>)进行排序。

逐行解释

struct sorter_type {
    // 自定义排序规则
    bool operator()(const std::vector<int>& lhs, const std::vector<int>& rhs) {
        return /* something */; // 这里写排序条件,返回 true 表示 lhs 排在 rhs 前面
    }
} sorter;
  • struct sorter_type 定义了一个函数对象(仿函数)
  • operator()调用运算符重载,允许这个结构体的对象像函数一样被调用。
  • lhsrhs 分别表示比较的两个元素,它们是 std::vector<int> 类型(即二维 vector 的一行)。
  • /* something */ 是排序条件,例如:
    • 按第一列升序:lhs[0] < rhs[0]
    • 按行内元素和升序:std::accumulate(lhs.begin(), lhs.end(), 0) < std::accumulate(rhs.begin(), rhs.end(), 0)
std::sort(vec.begin(), vec.end(), sorter);
  • std::sort 是 C++ 标准库的快速排序函数(实际上通常是 introsort)。
  • vec.begin()vec.end() 表示对 vec 中的所有行进行排序。
  • sorter 是前面定义的比较器对象,它决定了排序顺序。

举个例子

假设:

std::vector<std::vector<int>> vec = {{2, 5}, {1, 9}, {3, 4}};

如果比较器是:

bool operator()(const std::vector<int>& lhs, const std::vector<int>& rhs) {
    return lhs[0] < rhs[0]; // 按第一列升序
}

排序结果就是:

{ {1, 9}, {2, 5}, {3, 4} }

如果你愿意,我可以帮你写一个**同时支持多列排序(类似 Excel)**的比较器,让 /* something */ 变得更强大。这样二维向量就可以按“第一列优先、第二列次之”这种方式排序。

一个完整可运行的 C++ 例子,演示如何用自定义比较器对二维 std::vector 排序,并且支持多列排序(类似 Excel 的“第一列优先、第二列次之”规则)。

#include <iostream>
#include <vector>
#include <algorithm>  // std::sort
// 定义比较器(仿函数)
struct sorter_type {
    bool operator()(const std::vector<int>& lhs, const std::vector<int>& rhs) const {
        // 第一列优先排序
        if (lhs[0] != rhs[0]) {
            return lhs[0] < rhs[0];  // 第一列升序
        }
        // 如果第一列相等,则按第二列排序
        return lhs[1] < rhs[1];  // 第二列升序
    }
};
int main() {
    // 二维向量
    std::vector<std::vector<int>> vec = {{2, 5}, {1, 9}, {3, 4}, {1, 2}, {2, 1}};
    std::cout << "排序前:\n";
    for (auto& row : vec) {
        std::cout << row[0] << " " << row[1] << "\n";
    }
    // 排序
    std::sort(vec.begin(), vec.end(), sorter_type());
    std::cout << "\n排序后(按第一列升序,第一列相等时按第二列升序):\n";
    for (auto& row : vec) {
        std::cout << row[0] << " " << row[1] << "\n";
    }
    return 0;
}

输出结果

排序前:
2 5
1 9
3 4
1 2
2 1
排序后(按第一列升序,第一列相等时按第二列升序):
1 2
1 9
2 1
2 5
3 4

关键点

  1. sorter_type 是一个仿函数(函数对象),实现了 operator(),可以像函数一样调用。
  2. 先比较第一列,如果不同则直接决定顺序;如果相同,再比较第二列。
  3. std::sort 会根据比较器返回的 true/false 来调整元素位置。
    我还可以帮你写一个支持任意列优先级顺序的比较器,这样你不用每次手写 if 判断。

按顺序梳理一下这些幻灯片里的例子,然后结合最后一页的 call / yield / resume / return 做解释。

1. 普通函数调用流程

auto f() {
    int x = 0;
    // some code
    return x;
}
  • call:调用函数 f()
  • return:函数一次性执行到结尾,返回结果 x
  • 特点:函数执行是不可中断的,调用一次就执行完毕退出。

2. 静态变量实现“记忆”功能

int f() {
    static int i = 0;
    return i++;
}
  • 每次调用 f() 都会在上一次的基础上递增 i
  • 这里 static int i 存储在全局静态区,所以函数的多次调用共享这个状态。
  • 缺点是:这是全局状态,会带来可维护性问题。

3. 类的状态封装

class my_generator {
    int i = 0;
public:
    int operator()() {
        return i++;
    }
};
  • 用对象成员变量 i 存储状态,这样多个生成器实例互不干扰。
  • 本质上是手动实现了一个状态机

4. Lambda 捕获状态

auto g = [i = 0]() mutable {
    return i++;
};
  • Lambda 捕获 i 并且用 mutable 允许修改它。
  • 这样 g() 每次调用都会延续上次的 i 值。

5. 进入协程思想

普通函数是:

call → run to completion → return

协程允许:

call → run until yield → 暂停(保存状态)
→ resume(从暂停位置继续) → ...
→ 最终 return

也就是:

  • yield:把一个中间值返回给调用方,但保留函数内部的执行状态
  • resume:下次调用时,从 yield 停下的位置继续执行。
  • return:最终结束并返回最终值。

6. 对比

  • 普通函数:一次性执行完毕 → 返回 → 状态丢失。
  • 静态变量 / 类 / Lambda:通过变量手动保存状态。
  • 协程:编译器帮你自动保存和恢复状态,让“暂停-继续”变得自然。
    如果你愿意,我可以帮你把最后的
call → yield → resume → return

成一个可运行的 C++20 协程示例,让这个流程直观跑出来看效果。

标题是 “Design of a Separable Transition-Diagram Compiler”,介绍了一种可分离、可高效运行的 COBOL 编译器设计方法。

背景

  • 当时的计算机(如 Burroughs 220)内存很小(几千字),磁带为主要存储介质。
  • COBOL 编译器通常很大,需要多次扫描(多 pass),而且被认为很复杂。
  • 这篇论文提出了一个快速、一遍(one-pass)的语法驱动编译器设计,可以在中等规模计算机上高效运行。

主要观点(反驳三种当时的主流看法)

  1. 语法驱动编译器速度慢 → 他证明可以很快(>100 张源代码卡片/分钟)。
  2. 编译器必须用编译器写 → 他展示了可以用其他方式构建高效编译器。
  3. COBOL 编译器必须很复杂 → 他的设计紧凑且易于移植。

核心技术

  1. 可分离(Separable)程序组织
    • 一个程序是可分离的,如果它被拆成多个处理模块(processing segments),这些模块可以分别存储、加载、运行,而不需要一次性全部载入。
    • 这样可以适配不同内存大小的机器:
      • 大内存 → 所有模块驻留内存。
      • 小内存 → 模块可存储在磁带/外存,需要时加载。
  2. 协程方法(Coroutine method)
    • 用协程将不同的编译阶段解耦,例如词法分析器和语法分析器互为生产者/消费者,通过协程切换而不是函数调用传递数据。
    • 这样可以避免复杂的状态机回溯,提高速度。
  3. 状态转移图(Transition Diagrams)
    • 用状态机图直接描述语法规则,让语法分析器高效且结构清晰。

实际效果

  • 在 5000 字内存的 Burroughs 220 上测试:
    • 语法检查速度可达 每分钟 140 张穿孔卡
    • 估算在 10000 字内存的机器上可实现完整的一遍编译,速度 超过每分钟 100 张卡

小结

  • 这个设计的亮点是:
    • 可分离结构(适配不同硬件)
    • 协程驱动的阶段切换(高效的数据流)
    • 用状态图直接做语法分析(直观、易实现)
  • 它不止适用于 COBOL,也适合 ALGOL 等其他语言编译器。
    如果你愿意,我可以帮你画一张 “协程 + 可分离编译器” 工作原理图,把它在 1963 年是怎么运作的直观画出来,方便理解它和现代协程编译器的联系。

这个例子看起来像是在描述一个生成器(generator)函数的行为,但这里的 yield 在 C++ 里并不是标准关键字(这是更像 Python、C#、或者 C++20 协程里的语法)。

代码理解(按生成器语义解释)

int f() {
    int i = 0;
    while (true)
        yield i++;  // 每次调用返回当前 i,然后 i 自增
}
int main() {
    std::cout << f() << '\n'; // 第一次调用 f(),返回 0
    std::cout << f() << '\n'; // 第二次调用 f(),返回 1
    std::cout << f() << '\n'; // 第三次调用 f(),返回 2
}
// 输出:
// 0
// 1
// 2

含义

  1. yield 表示挂起并返回一个值,下次继续执行时会从挂起处恢复。
  2. i 是生成器的内部状态,会在多次调用之间保留。
  3. 因为 i 从 0 开始,每次 yield i++
    • 第一次 → 返回 0
    • 第二次 → 返回 1
    • 第三次 → 返回 2
      以此类推。

在 C++ 里怎么实现?

  • 如果是 C++20 协程,可以用 co_yield 来实现类似效果。
  • 如果是手动实现,可以用静态变量或迭代器对象保存状态。
    例如用 static 模拟:
int f() {
    static int i = 0; // 静态变量,跨调用保存
    return i++;
}

输出同样是:

0
1
2

如果你需要的话,我可以帮你把这个 yield 版本用 C++20 协程语法 co_yield 改写,这样就完全能在现代 C++ 里跑起来了。

这个代码逐层剖开,这样你就能完全理解它是怎么模拟 Python/C# 里的 yield 效果的。

#include <coroutine>
#include <iostream>
#include <optional>
// 模板生成器类,支持生成类型为 T 的值
template <typename T>
struct Generator {
    // 协程 promise 类型:控制协程的状态和数据
    struct promise_type {
        T current_value;  // 存储当前生成的值
        // 当协程中执行 co_yield 时调用
        // 保存当前值,并挂起协程
        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }
        // 协程开始时立即挂起,等待外部第一次 resume
        std::suspend_always initial_suspend() { return {}; }
        // 协程结束时挂起(final_suspend 保证 handle 可被安全销毁)
        std::suspend_always final_suspend() noexcept { return {}; }
        // 返回协程句柄包装成的 Generator 对象
        Generator get_return_object() {
            return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        // 协程返回时不返回值
        void return_void() {}
        // 如果协程中抛出异常,直接终止程序
        void unhandled_exception() { std::terminate(); }
    };
    std::coroutine_handle<promise_type> handle;  // 协程句柄
    // 构造函数:保存协程句柄
    Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
    // 析构函数:销毁协程,释放资源
    ~Generator() {
        if (handle) handle.destroy();
    }
    // 获取下一个值
    std::optional<T> next() {
        // 如果协程句柄无效或已结束,返回空
        if (!handle || handle.done()) return std::nullopt;
        // 恢复协程执行,直到下一个 co_yield
        handle.resume();
        // 如果恢复后协程结束了,返回空
        if (handle.done()) return std::nullopt;
        // 返回当前生成的值
        return handle.promise().current_value;
    }
};
// 用协程生成器实现 yield 效果
Generator<int> f() {
    int i = 0;
    while (true)
        co_yield i++; // 每次挂起并返回当前 i,下一次恢复时 i 自增
}
int main() {
    auto gen = f();  // 获取生成器对象(协程处于初始挂起状态)
    std::cout << *gen.next() << '\n';  // 恢复协程,输出 0
    std::cout << *gen.next() << '\n';  // 再次恢复协程,输出 1
    std::cout << *gen.next() << '\n';  // 再次恢复协程,输出 2
}
main Generator Coroutine (f) promise_type Call f() Create promise_type get_return_object() creates Generator with handle initial_suspend() returns suspend_always Return Generator object (suspended) Coroutine suspended at initial_suspend Call next() handle.resume() Execute co_yield i++ (i=0) yield_value(0) stores current_value=0, suspend_always Coroutine suspended Return std::optional<int>(0) std::cout << 0 << '\n' Call next() handle.resume() Execute co_yield i++ (i=1) yield_value(1) stores current_value=1, suspend_always Coroutine suspended Return std::optional<int>(1) std::cout << 1 << '\n' Call next() handle.resume() Execute co_yield i++ (i=2) yield_value(2) stores current_value=2, suspend_always Coroutine suspended Return std::optional<int>(2) std::cout << 2 << '\n' Generator destructor called handle.destroy() Destroy coroutine main Generator Coroutine (f) promise_type
sequenceDiagram
    participant M as main
    participant G as Generator
    participant C as Coroutine (f)
    participant P as promise_type
    M->>C: Call f()
    C->>P: Create promise_type
    P->>G: get_return_object() creates Generator with handle
    P-->>C: initial_suspend() returns suspend_always
    C-->>M: Return Generator object (suspended)
    Note over C: Coroutine suspended at initial_suspend
    M->>G: Call next()
    G->>C: handle.resume()
    C->>P: Execute co_yield i++ (i=0)
    P-->>C: yield_value(0) stores current_value=0, suspend_always
    C-->>G: Coroutine suspended
    G-->>M: Return std::optional<int>(0)
    M->>M: std::cout << 0 << '\n'
    M->>G: Call next()
    G->>C: handle.resume()
    C->>P: Execute co_yield i++ (i=1)
    P-->>C: yield_value(1) stores current_value=1, suspend_always
    C-->>G: Coroutine suspended
    G-->>M: Return std::optional<int>(1)
    M->>M: std::cout << 1 << '\n'
    M->>G: Call next()
    G->>C: handle.resume()
    C->>P: Execute co_yield i++ (i=2)
    P-->>C: yield_value(2) stores current_value=2, suspend_always
    C-->>G: Coroutine suspended
    G-->>M: Return std::optional<int>(2)
    M->>M: std::cout << 2 << '\n'
    Note over G: Generator destructor called
    G->>C: handle.destroy()
    C->>P: Destroy coroutine

1. 总体功能

这个代码用 C++20 协程 实现了一个“生成器”(Generator),能像 Python 的 yield 一样,每次调用 next() 生成一个新的值,并记住上一次的状态。

Generator<int> f() {
    int i = 0;
    while (true) co_yield i++;
}
  • 第一次调用 next() → 返回 0
  • 第二次调用 next() → 返回 1
  • 第三次调用 next() → 返回 2
  • 状态不会丢失,i 会继续递增

2. 协程核心结构

promise_type

struct promise_type {
    T current_value;
    std::suspend_always yield_value(T value) {
        current_value = value;
        return {};
    }
    ...
};
  • promise_type 是 C++ 协程的核心协议,编译器自动帮你连接协程逻辑和外部控制代码。
  • yield_value(T)co_yield 触发时调用的函数
    • 把值保存到 current_value
    • 返回 std::suspend_always → 让协程暂停,等待外部再次恢复

协程生命周期

std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
  • initial_suspend() → 控制协程启动时是否立即暂停,这里选择暂停,等你第一次 resume() 再开始
  • final_suspend() → 控制协程结束后是否暂停,这里也暂停,方便做清理

协程返回对象

Generator get_return_object() {
    return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
  • f() 被调用时,编译器会自动生成一个 promise_type,并调用这个函数返回 Generator 对象
  • std::coroutine_handle 是一个可以恢复/销毁协程的指针

3. Generator 本身

std::optional<T> next() {
    if (!handle || handle.done()) return std::nullopt;
    handle.resume();  // 恢复协程执行
    if (handle.done()) return std::nullopt;
    return handle.promise().current_value;
}
  • handle.resume() 让协程从上一次 co_yield 暂停的地方继续执行
  • 协程遇到 co_yield → 保存值 → 暂停 → 返回给调用者
  • 下次调用 next() 又会从暂停处继续往下执行

4. f() 生成器函数

Generator<int> f() {
    int i = 0;
    while (true) co_yield i++;
}
  • 这是一个无限生成整数的协程
  • 每次 co_yield 暂停,i 的值被保存到 current_value
  • next() 恢复后,i 继续递增,不会重新从 0 开始

5. 执行流程(以三次 next() 为例)

  1. f() 被调用 → 返回一个暂停的协程对象 gen
  2. 第一次 next()
    • 恢复到 int i = 0co_yield 0
    • 暂停并返回 0
  3. 第二次 next()
    • co_yield 之后继续 → i = 1co_yield 1
    • 暂停并返回 1
  4. 第三次 next()
    • co_yield 之后继续 → i = 2co_yield 2
    • 暂停并返回 2

6. 输出结果

0
1
2

如果你愿意,我可以帮你画一个协程状态切换时序图,你会更直观地理解 co_yield 是如何暂停/恢复的。这样你以后写生成器会非常顺手。

这一长段内容,其实是 Timur Doumler 的一个演讲/讲义里关于 C++ 协程(coroutine)和生成器(generator)的例子和解释,我帮你分段整理成理解版:

1. 从最朴素的 yield 想法开始

int f() {
    int i = 0;
    while (true)
        yield i++;
}

这个是“假想语法”,不是 C++ 标准的东西,意思是“每次调用 f(),返回当前的 i,然后挂起,下次再调用时接着执行 i++ 后的值”。

但 C++ 没有 yield 关键字,所以这只是概念演示。

2. 用 C++20 co_yield 语法改写

int f() {
    int i = 0;
    while (true)
        co_yield i++;
}

co_yield 是 C++ 协程的一个关键字,语义和 yield 类似:

  • 返回一个值
  • 挂起协程执行
  • 等下一次恢复协程时从挂起点继续执行

但是,这样写在 C++20 是编译不过的,因为 C++20 协程语法只是“机制”,没有内置生成器类型,你需要自己定义返回类型(如 my_generator<int>)才能用。

3. 使用自定义生成器类型

my_generator<int> f() {
    int i = 0;
    while (true)
        co_yield i++;
}
int main() {
    auto g = f();
    std::cout << g() << '\n';
    std::cout << g() << '\n';
    std::cout << g() << '\n';
}
// 输出:0 1 2

这里 my_generator<int> 是用户自定义的类型,内部实现了:

  • promise_type(协程承诺对象)
  • std::coroutine_handle
  • 迭代接口(或调用运算符 ()

这样 g() 每次调用都会从协程挂起点恢复,继续执行 i++

4. 实现原理(协程框架)

协程会被编译器拆分成:

  • 用户代码(你写的逻辑)
  • promise_type(协程状态管理)
  • std::coroutine_handle(控制协程生命周期)
  • 协程帧(Coroutine frame)(保存局部变量、挂起点、返回值等)
    协程挂起后,会把 i 等局部变量保存在协程帧中,下次恢复时直接接着运行。

5. 如果不用协程,也能实现类似效果

int main() {
    auto g = [i = 0]() mutable {
        return i++;
    };
    std::cout << g() << '\n';
    std::cout << g() << '\n';
    std::cout << g() << '\n';
}
// 输出:0 1 2

用闭包捕获变量,也能实现“状态保存”的效果,只是没有 co_yield 的挂起/恢复语义,也不能异步。

6. 现实情况

  • C++20 只是提供了协程机制,没有内置生成器类型。
  • C++23 开始标准库引入了 std::generator

这段代码,是 Timur Doumler 讲解如何在 C++20/experimental 协程里自己实现生成器(generator)的例子。我帮你逐步解读:

1. promise_type 的作用

template <typename T>
struct promise_type {
    T current_value;                        // 保存协程当前生成的值
    auto get_return_object() {
        return generator(*this);            // 返回与 promise 绑定的 generator 对象
    }
    auto initial_suspend() {
        return std::experimental::suspend_always{};  // 协程刚开始时挂起
    }
    auto final_suspend() {
        return std::experimental::suspend_always{};  // 协程结束时挂起(可被销毁)
    }
    auto yield_value(const T& value) {
        current_value = value;              // 保存 yield 的值
        return std::experimental::suspend_always{};  // 每次 co_yield 后挂起
    }
    void return_void() {}                    // 协程没有返回值
    void unhandled_exception() {
        std::terminate();                    // 协程内部异常处理
    }
};

理解

  • promise_type 是协程的核心状态管理类。
  • current_value 保存每次 co_yield 的值。
  • initial_suspendfinal_suspend 控制协程开始/结束时是否挂起。
  • yield_valueco_yield 时被调用,保存值并挂起。
  • get_return_object 返回一个和 promise 绑定的 generator 对象给用户使用。

2. generator 类型

template <typename T>
struct generator {
    struct promise_type;                        // 前置声明
    using handle_type = std::coroutine_handle<promise_type>;  // 协程句柄
    handle_type handle;                         // 持有协程状态的句柄
    generator(promise_type& promise)
        : handle(handle_type::from_promise(promise)) {}  // 根据 promise 构造句柄
    generator(const generator&) = delete;       // 禁止拷贝
    generator& operator=(const generator&) = delete;
    generator(generator&& other)                 // 移动构造
        : handle(other.handle) {
        other.handle = nullptr;
    }
    generator& operator=(generator&& other) {   // 移动赋值
        if (this != &other) {
            handle = other.handle;
            other.handle = nullptr;
        }
        return *this;
    }
    ~generator() {                              // 析构时销毁协程
        if (handle) handle.destroy();
    }
    T operator()() {                             // 调用运算符,用于获取下一个值
        assert(handle != nullptr);
        handle.resume();                         // 恢复协程执行
        return handle.promise().current_value;   // 返回当前 yield 的值
    }
};

理解

  • generator 是一个用户可以直接使用的“生成器对象”。
  • 内部持有一个 std::coroutine_handle<promise_type> 来控制协程的挂起/恢复。
  • 禁止拷贝,只允许移动。
  • 调用 operator() 会让协程继续执行到下一个 co_yield,然后返回生成的值。
  • 析构时,协程状态会被销毁,避免内存泄漏。

3. 协程运行流程

  1. co_yield 被调用 → yield_value 保存值并挂起协程。
  2. generator::operator() 被调用 → 恢复协程执行。
  3. 协程继续执行下一条语句,直到再次 co_yield 或结束。
  4. 协程结束 → final_suspend 被触发,可安全销毁。

4. 核心特点

  • 保存局部变量状态(i++)无需全局变量。
  • 可多次调用 generator() 获取连续值。
  • 和 Python、C# 的生成器语义类似。
  • 在 C++20 中需要自己定义生成器类型(C++23 将引入标准库生成器)。
    如果你愿意,我可以帮你写一个完整可编译的例子,直接使用这个 generator 类生成 0、1、2 的序列,让你可以马上运行和测试。
main() generator<int> promise_type auto g = f() // 创建协程 get_return_object() 协程对象生成,尚未执行 g() // 第一次调用 resume() // 执行协程直到第一个 co_yield current_value = 0 suspend return current_value (0) g() // 第二次调用 resume() // 从上次暂停点继续 current_value = 1 suspend return current_value (1) g() // 第三次调用 resume() // 从上次暂停点继续 current_value = 2 suspend return current_value (2) main() generator<int> promise_type
sequenceDiagram
    participant Main as main()
    participant Generator as generator<int>
    participant Promise as promise_type
    Main->>Generator: auto g = f()  // 创建协程
    Generator->>Promise: get_return_object()
    Note over Generator,Promise: 协程对象生成,尚未执行
    Main->>Generator: g()  // 第一次调用
    Generator->>Promise: resume()  // 执行协程直到第一个 co_yield
    Promise->>Promise: current_value = 0
    Promise-->>Generator: suspend
    Generator-->>Main: return current_value (0)
    Main->>Generator: g()  // 第二次调用
    Generator->>Promise: resume()  // 从上次暂停点继续
    Promise->>Promise: current_value = 1
    Promise-->>Generator: suspend
    Generator-->>Main: return current_value (1)
    Main->>Generator: g()  // 第三次调用
    Generator->>Promise: resume()  // 从上次暂停点继续
    Promise->>Promise: current_value = 2
    Promise-->>Generator: suspend
    Generator-->>Main: return current_value (2)

你给出的例子是一个 典型的编译器流水线(reader → lexer → parser)使用 C++ 协程生成器 (co_yield) 的模式。下面我帮你用详细理解一下每一层:

1⃣ Reader 层

generator<char> get_char() {
  while (!eof) {
    // 读取下一个字符
    co_yield c;
  }
}
  • 作用:从输入流中逐个字符读取数据。
  • 机制
    1. 每次 co_yield c 会把当前字符返回给调用者,同时暂停协程。
    2. 下次调用生成器时,从暂停点继续读取下一个字符。
  • 特点
    • 类似于“惰性读取”,不会一次性把整个文件读到内存。
    • 对于大文件或流式输入非常适用。

2⃣ Lexer(词法分析器)

generator<token> get_token() {
  while (auto c = get_char()) {
    // 根据字符生成 token
    co_yield t;
  }
}
  • 作用:把字符流转换为 token 流(词法单元)。
  • 机制
    1. 内部调用 get_char() 从 reader 获取字符。
    2. 解析字符构造 token,例如标识符、数字、符号等。
    3. 每次 co_yield t 返回一个 token 并暂停。
  • 特点
    • 也是惰性生成 token,每次只生成一个 token。
    • 可以直接用 for/while 遍历生成器,无需一次性生成整个 token 列表。

3⃣ Parser(语法分析器)

generator<ast_node> parse_expression() {
  auto t = get_token();
  // 根据 token 解析表达式
  co_yield node;
}
  • 作用:把 token 流解析为 AST(抽象语法树)节点。
  • 机制
    1. 内部调用 get_token() 获取 token。
    2. 按语法规则生成 AST 节点。
    3. 每次 co_yield node 返回一个 AST 节点并暂停。
  • 特点
    • 可以边解析边处理 AST,支持流式处理。
    • 不需要先把整个输入解析完再处理结果。

总结

  • 这是一个 三层流水线
    1. Reader:字符流
    2. Lexer:token 流
    3. Parser:AST 流
  • 优势
    • 使用协程和 co_yield 可以做到懒计算(lazy evaluation)
    • 每一层都可以暂停和恢复,处理大输入时效率高。
    • 逻辑清晰,每层只关注自己任务,耦合度低。
#include <coroutine>
#include <iostream>
#include <string>
#include <cassert>
// -------------------- 通用 generator --------------------
template <typename T>
struct generator {
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    handle_type handle;
    struct promise_type {
        T current_value;
        auto get_return_object() { return generator{*this}; }
        auto initial_suspend() { return std::suspend_always{}; }
        auto final_suspend() noexcept { return std::suspend_always{}; }
        auto yield_value(const T& value) {
            current_value = value;
            return std::suspend_always{};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
    generator(promise_type& p) : handle(handle_type::from_promise(p)) {}
    generator(const generator&) = delete;
    generator& operator=(const generator&) = delete;
    generator(generator&& other) : handle(other.handle) { other.handle = nullptr; }
    generator& operator=(generator&& other) {
        if (this != &other) {
            handle = other.handle;
            other.handle = nullptr;
        }
        return *this;
    }
    ~generator() {
        if (handle) handle.destroy();
    }
    T operator()() {
        assert(handle != nullptr);
        handle.resume();
        return handle.promise().current_value;
    }
};
// -------------------- Reader: 生成字符 --------------------
generator<char> get_char(const std::string& input) {
    for (char c : input) co_yield c;
}
// -------------------- Lexer: 生成 token --------------------
enum class TokenType { Number, Plus, Minus, Unknown };
struct Token {
    TokenType type;
    char value;
};
generator<Token> get_token(generator<char>& chars) {
    while (true) {
        char c = chars();
        if (c == '\0') break;  // 假设 '\0' 表示结束
        if (isdigit(c))
            co_yield Token{TokenType::Number, c};
        else if (c == '+')
            co_yield Token{TokenType::Plus, c};
        else if (c == '-')
            co_yield Token{TokenType::Minus, c};
        else
            co_yield Token{TokenType::Unknown, c};
    }
}
// -------------------- Parser: 生成 AST 节点 --------------------
struct ASTNode {
    std::string type;
    char value;
};
generator<ASTNode> parse_expression(generator<Token>& tokens) {
    while (true) {
        Token t = tokens();
        if (t.type == TokenType::Number)
            co_yield ASTNode{"Number", t.value};
        else if (t.type == TokenType::Plus)
            co_yield ASTNode{"Plus", t.value};
        else if (t.type == TokenType::Minus)
            co_yield ASTNode{"Minus", t.value};
        else
            break;  // 遇到 Unknown 或结束
    }
}
// -------------------- 主函数 --------------------
int main() {
    std::string input = "1+2-3";
    auto char_gen = get_char(input);
    auto token_gen = get_token(char_gen);
    auto ast_gen = parse_expression(token_gen);
    std::cout << "AST nodes:\n";
    for (int i = 0; i < 5; ++i) {  // 遍历前 5 个节点
        ASTNode node = ast_gen();
        std::cout << node.type << " -> " << node.value << "\n";
    }
    return 0;
}
main ast_gen (generator<ASTNode>) parse_expression token_gen (generator<Token>) char_gen (generator<char>) promise_type 调用 get_char("1+2-3") 创建 promise_type initial_suspend() 挂起 返回 char_gen 字符生成器初始挂起 调用 get_token(char_gen) 创建 promise_type initial_suspend() 挂起 返回 token_gen 标记生成器初始挂起 调用 parse_expression(token_gen) 创建 promise_type initial_suspend() 挂起 返回 ast_gen AST 生成器初始挂起 ast_gen() handle.resume() token_gen() char_gen() yield_value('1') 存储 current_value='1' suspend_always 挂起 返回 '1' yield_value(Token{Number, '1'}) suspend_always 挂起 返回 Token{Number, '1'} yield_value(ASTNode{"Number", '1'}) suspend_always 挂起 返回 ASTNode{"Number", '1'} 返回 ASTNode{"Number", '1'} 输出 "Number ->> 1" ast_gen() handle.resume() token_gen() char_gen() yield_value('+') 存储 current_value='+' suspend_always 挂起 返回 '+' yield_value(Token{Plus, '+'}) suspend_always 挂起 返回 Token{Plus, '+'} yield_value(ASTNode{"Plus", '+'}) suspend_always 挂起 返回 ASTNode{"Plus", '+'} 返回 ASTNode{"Plus", '+'} 输出 "Plus ->> +" ast_gen() handle.resume() token_gen() char_gen() yield_value('2') 存储 current_value='2' suspend_always 挂起 返回 '2' yield_value(Token{Number, '2'}) suspend_always 挂起 返回 Token{Number, '2'} yield_value(ASTNode{"Number", '2'}) suspend_always 挂起 返回 ASTNode{"Number", '2'} 返回 ASTNode{"Number", '2'} 输出 "Number ->> 2" main 退出 销毁 ast_gen handle.destroy() 销毁 token_gen handle.destroy() 销毁 char_gen main ast_gen (generator<ASTNode>) parse_expression token_gen (generator<Token>) char_gen (generator<char>) promise_type

你贴的三段图展示了 C++ 协程(普通 generator 与异步 generator) 的执行流程,以及它们如何解决传统回调嵌套的问题。下面我用逐段解释:

1⃣ 普通 generator (my_generator<T>)

     call  ───────────────────────▶ my_generator<T> f() {
                                        //
yield ◀────────────────────┐            // code
                           └──────────┌───────────────┐
                                      │ co_yield x;   │
                           ┌─────────▶└───────────────┘
resume ────────────────────┘
                                       // more code
                      ┌──────────────  co_return x;
                      │
return ◀──────────────┘             }

理解:

  1. call:调用生成器 f(),协程开始执行。
  2. yield:执行到 co_yield x 时暂停,将 x 返回给调用者。
  3. resume:下一次调用生成器时,从暂停点继续执行。
  4. co_return:协程结束,返回最终结果。

这就是一个同步 generator 的基本控制流:可以生成一系列值,每次调用返回一个。

2⃣ 异步 generator (async_generator<T> + co_await)

 call  ───────────────────────▶ async_generator<T> f1() {                           async_generator<U> f2() {
                                    // code                         await               // code
suspend   ◀──────────┐           ┌─────────────────────────────┐────────────────────▶┌─────────────────┐
                     └────────── │  auto u = co_await f2();    │                     │  co_yield u;    │
                                 └─────────────────────────────┘ ◀───────────────────└─────────────────┘
                                    // more code                   resume           }
                    ┌───────────    co_return u;
 return ◀───────────┘
                                }

理解:

  1. f1 调用 f2 并使用 co_await
    • 协程 f1co_await挂起,等待 f2 生成值。
  2. f2 执行 co_yield
    • 返回值 uf1
  3. resume
    • f1 继续执行 co_await 后的代码。
  4. co_return
    • f1 协程结束,返回最终值。

与同步 generator 不同,异步 generator 可以在等待另一个协程时挂起,不阻塞线程。

3⃣ 异步协程 + 多线程 (解决 callback hell)

                                                                                                             Thread 1
        call  ───────────────────────▶ async_generator<T> f1() {
                                                                         register
                                           // code                       callback
       suspend   ◀──────────┐                                          ────────────────────▶ async_generator<U> f2() {
                            └──────────    auto u = co_await f2();
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                                           // more code                 ◀───────────────────     // code
                                                                          call
                            ┌──────────    return u;                      callback               co_yield u;
          return ◀──────────┘
                                       }                                                     }
                                                                                                              Thread 2

场景概述

  • 有两个异步协程:
    1. f1(返回 async_generator<T>)在 Thread 1
    2. f2(返回 async_generator<U>)在 Thread 2
  • f1 中使用 co_await f2(),意味着 f1 会等待 f2 生成值。

执行流程

1⃣ Thread 1: 调用 f1

call ──────────────▶ async_generator<T> f1()
  • f1 开始执行。
  • 执行一些代码(// code)。
  • 遇到 auto u = co_await f2();挂起,因为它等待 f2 的结果。
suspend ◀──────────┐
                   └── f1 挂起

2⃣ Thread 2: 执行 f2

────────────────────▶ async_generator<U> f2()
  • f2 在 Thread 2 注册了一个 callback(事件触发时调用)。
  • 执行 // code 后通过 co_yield u 生成值。
  • 这个值会被 f1co_await 捕获,触发 f1 的恢复。

3⃣ Thread 1: 恢复 f1

resume
  • f1 接收到 f2 的结果 u
  • f1 继续执行 co_await 后的代码(// more code)。
  • 最终通过 return u;co_return 结束协程。
return ◀──────────┘

总结

  1. Thread 1 调用 f1,遇到 co_await f2() 挂起。
  2. Thread 2 执行 f2,生成值 u 并通过 co_yield 返回。
  3. Thread 1 恢复执行,得到 f2 的结果,继续 f1 的逻辑。

这一系列函数都是在判断一个整数是否是 2 的幂次方。我来详细解释:

函数原理

函数形式:

#include <iostream>
template <typename T>
bool is_power_of_2(T i) {
    //
    return i > 0 && (i & (i - 1)) == 0;
}
int main() {
    int num = 4;
    std::cout << is_power_of_2(4) << '\n';
}

步骤解析

  1. i > 0
    • 2 的幂次方都是正整数,所以首先判断 i 是否大于 0。
    • 如果 i <= 0,直接返回 false
  2. i & (i - 1)
    • 这是判断是否是 2 的幂的经典位运算技巧。
    • 原理:
      • 2 的幂的二进制表示中,只有一位是 1,其余都是 0。
        例如:
        • 1 → 0001
        • 2 → 0010
        • 4 → 0100
        • 8 → 1000
      • 当你做 i - 1 时:
        • 1 → 0 → 0000
        • 2 → 1 → 0001
        • 4 → 3 → 0011
        • 8 → 7 → 0111
      • 再与原数做按位与 i & (i - 1)
        • 2 的幂次方的结果总是 0
        • 非 2 的幂次方会至少有一位为 1,不为 0。
  3. 组合起来
    • i > 0 && (i & (i - 1)) == 0
    • 只有正整数且满足 i & (i-1) == 0 时,才是 2 的幂。

为什么有很多重载

你列出的函数有:

short, unsigned short, int, unsigned int,
long, unsigned long, long long, unsigned long long,
char, signed char, unsigned char, wchar_t

这是为了 支持各种整数类型,保证不同类型的整数都可以调用 is_power_of_2

总结理解

  • 功能:判断一个整数是否是 2 的幂次方。
  • 方法:用位运算 (i & (i-1)) == 0 检测二进制中是否只有一位为 1。
  • 注意事项:0 或负数都不是 2 的幂。
  • 适用类型:支持所有常见整数类型,包括有符号和无符号类型。

两段代码是对 模板函数 is_power_of_2 的类型安全改进的例子,我来详细解析:

第一段代码

template <typename T>
bool is_power_of_2(T i) {
    return i > 0 && (i & (i - 1)) == 0;
}
int main() {
    std::cout << is_power_of_2(0.25);
    // error: invalid operands to binary expression
}

理解

  1. 你写了一个模板函数,理论上可以接受任意类型 T
  2. 但是 0.25double 类型(浮点数),而 (i & (i-1))位运算
  3. 浮点数不支持位运算,所以编译器报错:
    error: invalid operands to binary expression
    
  4. 也就是说,这个模板函数不适合浮点类型。

第二段代码

template <typename T>
bool is_power_of_2(T i) {
    static_assert(std::is_integral_v<T>);
    return i > 0 && (i & (i - 1)) == 0;
}
int main() {
    std::cout << is_power_of_2(0.25);
    // error: static_assert failed due to requirement 'std::is_integral_v<double>'
}

理解

  1. 使用 static_assert(std::is_integral_v<T>)
    • 编译期检查类型 T 是否是整数类型(int, long, unsigned int 等)。
    • 如果不是整数类型,编译器会报错。
  2. 当你传入 0.25double)时:
    • std::is_integral_v<double>false
    • static_assert 触发编译错误:
      static_assert failed due to requirement 'std::is_integral_v<double>'
      
  3. 优点:
    • 提前在编译期阻止浮点类型使用此函数。
    • 不会出现不合法的位运算错误,更安全。

总结

  • 原理:is_power_of_2 只对整数类型有效。
  • 第一段:没有类型限制,浮点数会在编译时报位运算错误。
  • 第二段:使用 static_assert 限制类型为整数,浮点数会在编译期报错,更安全。

代码分析

#include <iostream>
#include <cmath>
template <typename T>
bool is_power_of_2(T i) {
    static_assert(std::is_integral_v<T>);
    return i > 0 && (i & (i - 1)) == 0;
}
template <typename T>
bool is_power_of_2(T x) {
    static_assert(std::is_floating_point_v<T>);
    int exponent;
    const T mantissa = std::frexp(x, &exponent);
    return mantissa == T(0.5);
}
int main() { std::cout << is_power_of_2(0.25); }

理解

  1. 你定义了两个模板函数,名字都是 is_power_of_2
    • 第一个:针对整数类型,使用 位运算 判断是否是 2 的幂。
    • 第二个:针对浮点类型,使用 std::frexp 判断是否是 2 的幂。
  2. 问题:
    • C++ 模板函数 不能仅靠 static_assert 来区分重载
    • 编译器在解析模板时,看到两个 is_power_of_2(T) 都是同样的签名(同样的模板参数 T),所以报错:
      error: redefinition of function template ‘is_power_of_2’
      
  3. 根本原因:
    • static_assert 只在 实例化模板时 才会触发,但模板签名在编译器看来是相同的。
    • 因此编译器认为你 重新定义了同名模板函数

正确做法

在 C++ 中,要根据类型做模板区分,通常用 SFINAEstd::enable_if / concepts
例如 C++20 的写法:

#include <concepts>
#include <cmath>
#include <iostream>
// 整数版本
template <std::integral T>
bool is_power_of_2(T i) {
    return i > 0 && (i & (i - 1)) == 0;
}
// 浮点数版本
template <std::floating_point T>
bool is_power_of_2(T x) {
    int exponent;
    T mantissa = std::frexp(x, &exponent);
    return mantissa == T(0.5);
}
int main() {
    std::cout << is_power_of_2(8) << '\n';     // 整数,输出 1
    std::cout << is_power_of_2(0.25) << '\n';  // 浮点数,输出 1
}
  • std::integralstd::floating_pointconcepts
  • 编译器可以正确选择整数版本或浮点版本,避免模板重定义错误。

这段代码实现了 判断浮点数是否是 2 的幂,我们逐行理解如下:

template <std::floating_point T>
bool is_power_of_2(T x) {
  • 这是一个模板函数,要求 T 必须是浮点类型(如 floatdoublelong double)。
  • 这使用了 C++20 的 conceptsstd::floating_point 确保模板只能用在浮点类型上。
    int exponent;
    T mantissa = std::frexp(x, &exponent);
  • std::frexp 是一个标准库函数,用于 将浮点数分解成尾数和指数
    • 对于 x != 0,它返回一个 mantissaexponent,使得:
      x = mantissa * 2^exponent
      
    • 并且 mantissa 的范围是 [0.5, 1.0)
    • 例如:
      double m;
      int e;
      m = std::frexp(0.25, &e);
      // m = 0.5, e = -1
      
    return mantissa == T(0.5);
  • 判断尾数是否等于 0.5。
  • 为什么 0.5?
    • 因为如果一个浮点数是 2 的幂(如 1/8 = 0.125、8、16 等),frexp 会把它拆成 0.5 * 2^n 的形式。
    • 例子:
      • 8.0 → mantissa=0.5, exponent=4
      • 0.25 → mantissa=0.5, exponent=-1
    • 所以只要 mantissa == 0.5,就说明原数是 2 的幂。

总结理解

  1. 这个函数只针对浮点数。
  2. frexp 将数拆成尾数和指数。
  3. 如果尾数是 0.5,则说明这个浮点数是 2 的幂。
  4. 返回 truefalse
    好的,我给你的代码加上详细的注释,逐行说明每一部分的作用:
#include <cmath>    // 包含数学库,使用 std::frexp
#include <iostream> // 包含输入输出库,用于 std::cout
// -------------------- 整数版本 --------------------
// 对整数类型 T 判断是否为 2 的幂
template <typename T>
auto is_power_of_2(T i) -> std::enable_if_t<std::is_integral_v<T>, bool> {
    // std::enable_if_t<std::is_integral_v<T>, bool>:
    // 只有当 T 是整数类型时,这个函数模板才有效,否则编译失败
    // 判断方法:i > 0 && (i & (i - 1)) == 0
    // 原理:
    // - 2 的幂在二进制中只有一位是 1,减 1 后再与自身 & 运算结果为 0
    return i > 0 && (i & (i - 1)) == 0;
}
// -------------------- 浮点数版本 --------------------
// 对浮点类型 T 判断是否为 2 的幂
template <typename T>
auto is_power_of_2(T x) -> std::enable_if_t<std::is_floating_point_v<T>, bool> {
    // std::enable_if_t<std::is_floating_point_v<T>, bool>:
    // 只有当 T 是浮点类型时,这个函数模板才有效
    int exponent;                           // 用于接收 frexp 输出的指数
    const T mantissa = std::frexp(x, &exponent); // 将 x 拆解为 mantissa * 2^exponent
    // frexp 返回的 mantissa 范围为 [0.5, 1.0)
    // 如果 x 是 2 的幂,mantissa 一定等于 0.5
    return mantissa == T(0.5);
}
int main() {
    std::cout << is_power_of_2(0.25); // 调用浮点数版本
    // 0.25 = 2^-2, frexp(0.25) 返回 mantissa=0.5, exponent=-1
    // 所以输出 1 (true)
}

核心理解:

  1. 整数版本:用二进制特性 (i & (i-1)) == 0 判断 2 的幂。
  2. 浮点版本:用 frexp 拆分尾数和指数,尾数为 0.5 就是 2 的幂。
  3. enable_if:根据类型选择不同的模板实现,防止错误类型调用。

1⃣ is_power_of_2 多种写法演变

你展示了很多版本的 is_power_of_2 模板,从 C++11/14 的 enable_if 到 C++20 的 concepts:

(1) enable_if 方式
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
bool is_power_of_2(T i) { ... }
  • 这里 enable_if 用于 SFINAE(Substitution Failure Is Not An Error),限制模板只在 整数类型可用。
  • 同理还有浮点版本:
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
bool is_power_of_2(T x) { ... }
  • 问题:写法复杂,可读性差,而且容易出“重定义”错误,如果两个模板参数默认值相同。
(2) C++20 Concepts 方式
template <std::integral T>
bool is_power_of_2(T i) { ... }
template <std::floating_point T>
bool is_power_of_2(T x) { ... }
  • std::integralstd::floating_point 是 C++20 的 概念(concepts)
  • 更加简洁直观,编译器会在不满足条件时报错。
(3) 使用 auto + Concepts
bool is_power_of_2(std::integral auto i) { ... }
template <std::floating_point T>
bool is_power_of_2(T x) { ... }
  • 结合 auto 和 concepts,使调用更加简洁。
  • 编译器会根据参数类型选择正确的模板版本。

2⃣ 自定义 Concepts 示例

template <typename T>
concept arithmetic = std::integral<T> || std::floating_point<T>;
template <typename T>
concept my_number = arithmetic<T> && sizeof(T) <= 8;
  • arithmetic:约束 T 为整数或浮点数。
  • my_number:进一步约束 T 大小不超过 8 字节(排除 long double)。
  • 结合 auto
auto f(my_number auto x) { ... }
  • 调用示例:
auto x = f(2.0);  // OK,double 符合 my_number
auto y = f(2.0L); // error,long double 不符合 sizeof(T) <= 8

3⃣ hashable Concept

template <typename T>
concept hashable = requires(T t) {
    { std::hash<T>{}(t) } -> std::convertible_to<std::size_t>;
};
template <hashable T>
class hash_map;
  • 定义一个概念 hashable:要求类型 T 可以用 std::hash 生成 std::size_t
  • 限制模板类 hash_map 只能接受可哈希的类型作为键。

总结

  1. SFINAE + enable_if:C++11/14 的老方法,可行但繁琐。
  2. Concepts:C++20 新特性,更安全、可读性强。
  3. 自定义 Concepts:可定义复杂约束,如类型大小、是否可哈希。
  4. 模板函数选择:通过 concepts,编译器在编译期根据类型自动选择正确版本。

1⃣ 按年龄排序 std::sort / std::ranges::sort

struct User {
    std::string name;
    int age;
};
std::vector<User> users = { /* ... */ };
int main() {
    auto sort_by_age = [] (auto& lhs, auto& rhs) {
        return lhs.age < rhs.age;
    };
    std::sort(users.begin(), users.end(), sort_by_age);
}
  • std::sortusersage 排序。
  • 使用 lambda 表达式作为比较函数。
  • auto& lhs, auto& rhs 是 C++14/17 的通用 lambda 写法。
    C++20 ranges 改写:
std::ranges::sort(users, std::less{}, &User::age);
  • 使用 投影(projection),直接用 &User::age 表示按成员排序。
  • 更简洁,不用写 lambda。

2⃣ std::copy_if + std::transform 过滤和映射

bool underage(const User& user) { return user.age < 18; }
int main() {
    std::vector<User> filtered_users;
    std::copy_if(users.begin(), users.end(), std::back_inserter(filtered_users),
                 std::not_fn(underage)); // 保留 18 岁及以上
    std::transform(filtered_users.begin(), filtered_users.end(),
                   std::ostream_iterator<int>(std::cout, "\n"),
                   [] (const auto& user) { return user.age; });
}
  • std::copy_if 过滤出 成年用户(年龄 ≥ 18)。
  • std::transform 映射出 age 并打印。
  • std::not_fn(underage)underage 函数的逻辑反(成年用户)。

3⃣ C++20 ranges + views 改写

auto result = users
    | std::views::filter(std::not_fn(underage))
    | std::views::transform([] (const auto& user) { return user.age; });
std::ranges::copy(result, std::ostream_iterator<int>(std::cout, "\n"));
  • std::views::filter:延迟过滤,生成一个视图(view)。
  • std::views::transform:映射操作。
  • std::ranges::copy 输出到 std::ostream_iterator
  • 相比上一个例子,更现代化、延迟计算,不生成额外中间容器。

4⃣ range-based for 循环遍历 views

for (auto& age : users
        | std::views::filter(std::not_fn(underage))
        | std::views::transform([] (const auto& user) { return user.age; })) {
    std::cout << age << '\n';
}
  • 直接在 for 循环里遍历视图。
  • 避免显式调用 std::ranges::copy,语义清晰。

5⃣ iota 生成序列

for (int i : std::views::iota(0, 10))
    std::cout << i << '\n';
  • std::views::iota(0, 10) 生成一个 0 到 9 的整数序列
  • 类似 Python 的 range(0, 10)
  • C++20 的 ranges 提供了很多延迟生成序列的工具,非常方便。
    输出:
0
1
2
3
4
5
6
7
8
9

总结

  1. 排序
    • 传统 std::sort + lambda。
    • C++20 std::ranges::sort + 投影(projection)。
  2. 过滤 & 映射
    • 传统:std::copy_if + std::transform
    • C++20:std::views::filter + std::views::transform + std::ranges::copy 或 range-based for。
  3. 生成序列
    • std::views::iota 可以生成整数范围序列,延迟计算。
  4. 理念
    • C++20 ranges 追求 延迟计算 + 组合式 pipeline,避免中间容器。
    • views 提高代码可读性,表达逻辑更直观。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐