├── .gitignore
├── .gitattributes
├── .remarkrc
├── theme
├── assets
│ └── 1200px-ISO_C++_Logo.svg.png
└── main.html
├── requirements.txt
├── .editorconfig
├── package.json
├── .github
└── workflows
│ ├── lint.yml
│ └── build_doc.yml
├── .vscode
└── settings.json
├── Source
├── index.md
├── Chapter0
│ └── README.md
├── Chapter1
│ └── README.md
├── Chapter5
│ └── README.md
├── Chapter3
│ └── README.md
├── Chapter4
│ └── README.md
└── Chapter2
│ └── README.md
├── mkdocs.yml
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | /venv
4 | /node_modules
5 | /site
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.remarkrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "remark-preset-lint-mkdocs-material"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/theme/assets/1200px-ISO_C++_Logo.svg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nolongerwait/cpp_lambda_story_chinese_edition/HEAD/theme/assets/1200px-ISO_C++_Logo.svg.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | mkdocs-material-extras >= 0.0.5
2 |
3 | mkdocs-minify-plugin >= 0.5
4 | mkdocs-redirects >= 1.0
5 | mkdocs-git-revision-date-localized-plugin >= 1.0.0
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.yml]
13 | indent_size = 2
14 |
--------------------------------------------------------------------------------
/theme/main.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block scripts %}
4 | {{ super() }}
5 |
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "packageManager": "pnpm@8.15.1",
4 | "scripts": {
5 | "build": "mkdocs build",
6 | "dev": "mkdocs serve",
7 | "lint": "remark ./Source/**/*.md --quiet --frail",
8 | "lint:fix": "remark ./Source/**/*.md --quiet --frail --output"
9 | },
10 | "dependencies": {
11 | "remark-cli": "^10.0.1",
12 | "remark-preset-lint-mkdocs-material": "^0.6.0"
13 | },
14 | "devDependencies": {
15 | "pnpm": "^8.15.1",
16 | "taze": "^0.13.3"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json-schema.org/draft-07/schema#
2 | name: Lint
3 |
4 | on:
5 | push:
6 | branches:
7 | - main
8 | pull_request:
9 | branches:
10 | - main
11 |
12 | jobs:
13 | build:
14 | name: Lint documentation
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Checkout repository
18 | uses: actions/checkout@v2
19 |
20 | - uses: pnpm/action-setup@v2
21 | with:
22 | version: latest
23 |
24 | - name: Set up Node.js
25 | uses: actions/setup-node@v2
26 | with:
27 | node-version: 16
28 | cache: pnpm
29 |
30 | - name: Install Node dependencies
31 | run: |
32 | pnpm i
33 |
34 | - name: Lint
35 | run: |
36 | pnpm lint
37 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "files.associations": {
4 | ".remarkrc": "json"
5 | },
6 | "cSpell.words": [
7 | "arithmatex",
8 | "autohide",
9 | "betterem",
10 | "ceret",
11 | "escapeall",
12 | "fontawesome",
13 | "hardbreak",
14 | "inlinehilite",
15 | "linenums",
16 | "magiclink",
17 | "materialx",
18 | "mathjax",
19 | "mkdocs",
20 | "nbsp",
21 | "noopener",
22 | "progressbar",
23 | "pymdownx",
24 | "Roboto",
25 | "saneheaders",
26 | "smartsymbols",
27 | "striphtml",
28 | "superfences",
29 | "tasklist",
30 | "twemoji",
31 | "uslugify"
32 | ],
33 | "yaml.customTags": [
34 | "!ENV scalar",
35 | "!ENV sequence",
36 | "tag:yaml.org,2002:python/name:materialx.emoji.to_svg",
37 | "tag:yaml.org,2002:python/name:materialx.emoji.twemoji",
38 | "tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format",
39 | "tag:yaml.org,2002:python/name:pymdownx.slugs.uslugify",
40 | "tag:yaml.org,2002:python/name:pymdownx.arithmatex.inline_mathjax_format",
41 | "tag:yaml.org,2002:python/name:pymdownx.arithmatex.fence_mathjax_format"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/.github/workflows/build_doc.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json-schema.org/draft-07/schema#
2 | name: Build Doc
3 |
4 | on:
5 | push:
6 | branches:
7 | - main
8 | pull_request:
9 | branches:
10 | - main
11 | workflow_dispatch:
12 |
13 | jobs:
14 | build-doc:
15 | name: Build documentation
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Checkout repository
19 | uses: actions/checkout@v2
20 | with:
21 | fetch-depth: 0
22 |
23 | - name: Set up Python runtime
24 | uses: actions/setup-python@v3
25 | with:
26 | python-version: 3.9
27 |
28 | - name: Install Python dependencies
29 | run: |
30 | pip install -r requirements.txt
31 |
32 | - uses: pnpm/action-setup@v2
33 | with:
34 | version: latest
35 |
36 | - name: Set up Node.js
37 | uses: actions/setup-node@v2
38 | with:
39 | node-version: 16
40 | cache: pnpm
41 |
42 | - name: Install Node dependencies
43 | run: |
44 | pnpm i
45 |
46 | - name: Build
47 | env:
48 | GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }}
49 | ENABLE_MATHJAX: false
50 | run: |
51 | pnpm build
52 |
53 | - name: Deploy to gh-pages
54 | if: ${{ github.event_name != 'pull_request' }}
55 | uses: peaceiris/actions-gh-pages@v3
56 | with:
57 | github_token: ${{ secrets.GITHUB_TOKEN }}
58 | publish_dir: site
59 | force_orphan: true
60 | user_name: "github-actions[bot]"
61 | user_email: "github-actions[bot]@users.noreply.github.com"
62 | commit_message: 🚀 Deploying to gh-pages @ ${{ env.GITHUB_SHA }}
63 |
--------------------------------------------------------------------------------
/Source/index.md:
--------------------------------------------------------------------------------
1 | # C++ Lambda Story - From C++98 to C++20
2 |
3 | ## 介绍
4 |
5 | 本文为《C++ Lambda Story》的中文翻译,如果您觉得此书有价值,可以在 [https://leanpub.com/cpplambda](https://leanpub.com/cpplambda) 上支持下原作者。
6 |
7 | 或者如果您认识相关的翻译工作者或者出版社,可以积极联系原作者与出版社进行正规的中文翻译并出版。
8 |
9 | ## 译者
10 |
11 | | Chapter | Translator |
12 | | :----------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
13 | | [关于此书](./Chapter0/README.md) |
@nolongerwait |
14 | | [Lambda in C++98/03](./Chapter1/README.md) |
@nolongerwait |
15 | | [Lambda in C++11](./Chapter2/README.md) |
@nolongerwait |
16 | | [Lambda in C++14](./Chapter3/README.md) |
@nolongerwait |
17 | | [Lambda in C++17](./Chapter4/README.md) |
@Dup4 |
18 | | [Lambda in C++20](./Chapter5/README.md) |
@nolongerwait |
19 |
--------------------------------------------------------------------------------
/Source/Chapter0/README.md:
--------------------------------------------------------------------------------
1 | # 关于此书
2 |
3 | ## 成书渊源
4 |
5 | ## 阅读对象
6 |
7 | 本书适用于所有喜欢了解现代 C++ 特性:Lambda 表达式 的 C++ 开发人员。
8 |
9 | ## 读者反馈
10 |
11 | 如果您发现错误、拼写错误、语法错误……或其他任何需要更正的(特别是逻辑问题!),请将您的反馈发送到 bartlomiej.filipek@bfilipek.com。
12 |
13 | 您也可以使用这个地方:
14 |
15 | * [Leanpub Book 的反馈页面 - C++ Lambda Story](https://leanpub.com/cpplambda)
16 |
17 | 更重要的是,这本书在 *GoodReads* 上有一个专门的页面。请在那里分享您的见解:
18 |
19 | * [C++ Lambda Story @GoodReads](https://www.goodreads.com/book/show/53609731-c-lambda-story)
20 |
21 | ## 代码证书
22 |
23 | 这本书的代码在 **知识共享许可(Creative Commons License)** 下可用
24 |
25 | ## 代码格式
26 |
27 | ## 语法高亮限制
28 |
29 | ## 在线编译器
30 |
31 | 你可以使用一些在线编译器,这样就不用在本地创建项目来尝试运行和解读这些示例代码了。
32 |
33 | 这些在线编译器提供基础的文本编辑器,并且通常允许你自行编写源文件进行编译。
34 |
35 | 对于一些简短的代码而言,使用在线编译器来说是十分方便的,可以快速查看代码的运行结果,甚至你可以快速在不同版本,不同环境,不同编译器之间进行切换使用。
36 |
37 | 本书中大部分的代码都附有在线编译器的链接,当然,不同的代码使用的不同的编译器。
38 |
39 | 这是本书中所使用过的全部在线编译器服务:
40 |
41 | * [Coliru](http://coliru.stacked-crooked.com/)- 使用 GCC 9.2.0 版本(截止 2020 年 06 月),功能简洁但十分高效
42 | * [Wandbox](https://wandbox.org/)- 提供了大部分的编译器,包含了绝大多数的 Clang 和 GCC 版本,使用了 boost 的库,支持多文件编译。并且你可以生成链接来分享你的代码。
43 | * [Compiler Explorer](https://gcc.godbolt.org/)- 提供多种编译器,显示生成的汇编代码,可以执行代码,甚至进行静态代码分析。
44 | * [CppBench](https://quick-bench.com/)- 可以运行简单的 C++ 性能测试(基于 Google Benchmark)。
45 | * [C++ Insights](https://cppinsights.io/)- 基于 Clang 的 源码转义工具,可以展示编译器视角下的代码,比如将源代码进行预编译展开。你可以在这查看 Lambda 表达式,auto 关键字,结构化绑定,模板推断,可变参数包,范围式循环等的展开结果。
46 |
47 | 当然,如果想尝试其他 C++ 的在线编译器,你也可以在这个网站查看:[List of Online C++ Compilers by arnemertz](https://arnemertz.github.io/online-compilers/)
48 |
49 | ## 关于作者
50 |
51 | **Bartłomiej (Bartek) Filipekis**,一个拥有超过 12 年专业经验的 C++ 软件开发工程师。2010 年在 Cracow, Poland 毕业自 Jagiellonian University,拥有计算机科学的硕士学位。
52 |
53 | 现就职于 Xara,负责开发高级文档编辑器。
54 |
55 | 同时,拥有桌面图形程序、游戏开发、大型航空系统、图形驱动甚至生物反馈方面的开发经验。
56 |
57 | 早前,在 Cracow 当地的大学中教授编程(游戏编程和图形编程)课程。
58 |
59 | 从 2011 年起,Bartek 开始在 [bfilipek.com](http://bfilipek.com) 上撰写博客。
60 |
61 | 起初,博文主题围绕图形编程,但是现在更多聚焦于 C++ 核心内容。
62 |
63 | 同时,他也是 [Crocow C++](https://www.meetup.com/C-User-Group-Cracow/) 开发者组织的联合组织者。
64 |
65 | 你可以在 [@CppCast](https://cppcast.com/bartlomiej-filipek/) 找到他关于 C++17,博客和文本处理相关的内容。
66 |
67 | 从 2018 年 10 月起,Bartek 开始在 Polish National Body 就任 C++ 专家一职,这是一家直接与 ISO/IEC JTC 1/SC 22 (C++ Standardisation Committee) 工作的公司。
68 |
69 | 同月,Bartek 获得了 Microsoft 授予的 2019/2020 年度的 MVP 头衔荣誉。
70 |
71 | 在空闲时间,喜欢和他心爱的小儿子一起收集和拼装乐高模型。
72 |
73 | Bartek 也是《[C++ 17 In Detail](https://leanpub.com/cpp17indetail)》的作者。
74 |
75 | ## 致谢
76 |
77 | 如果没有 C++ 专家 Tomasz Kamiński 的宝贵意见,本书就不可能完成(参见 [Tomek 在 Linkedin 上的简介](https://www.linkedin.com/in/tomasz-kami%C5%84ski-208572b1/))。
78 |
79 | Tomek 在我们位于克拉科夫的 Local C++ 用户组中主持了关于 Lambda“历史”的现场编码演示:[Lambdas: From C++11 to C++20](https://www.meetup.com/pl-PL/C-User-Group-Cracow/events/258795519/)。
80 |
81 | 本书中使用的很多例子都来自那次会议。
82 |
83 | 尽管本书的初版相对较短,但后续扩展版本(额外的 100 页)是我从 JFT(John Taylor)那得到返回和鼓励的结果。
84 |
85 | John 花费了大量时间寻找可以改进和扩展的细节。
86 |
87 | 此外,我要对提供了很多有关 Lambda 返回内容的 [Dawid Pilarski](panicsoftware.com/about-me) 表示感谢。
88 |
89 | 最后也是相当重要的,我从博客读者、Patreon 论坛以及 C++ Polska 的讨论中获得了大量反馈和评论。
90 |
91 | 谢谢你们!
92 |
93 | ## 校阅历史
94 |
95 | * 2019 年 03 月 25 日 - 第一版上线!
96 | * 2020 年 01 月 05 日 - 语法、更好的例子、措辞、IIFE 部分、C++20 更新。
97 | * 2020 年 04 月 17 日 - C++20 章节重写、语法、措辞、布局。
98 | * 2020 年 04 月 30 日 - 从 C++11、C++17 和 C++20 中的 lambda 派生
99 | * 2020 年 06 月 19 日 - 主要更新:
100 | * 改进了 C++03 章节,添加了有关标准库中的辅助函数对象的部分。
101 | * 添加了有关如何操作的新部分从 C++14 章节中不推荐使用的 bind1stin 转换为现代替代方案。
102 | * C++11 和 C++17 章节中改进和扩展的 IFFE 部分
103 | * 带有 lambda 技术列表的新附录
104 | * 带有五大 lambda 功能列表的新附录,改编自博客文章
105 | * 带有更新副标题的新标题图片
106 | * 整本书的许多较小改进
107 | * 2020 年 08 月 03 日 - 主要更新,Kindle 版本上线可用:
108 | * 大多数代码示例现在在标题中都有指向在线编译器版本的链接
109 | * 改进了 Lambda 语法的描述
110 | * 在 C++17 和 C++20 章节中增添了新的内容。
111 | * 新部分:如何在容器中存储 lambda,Lambda 和异步执行,递归 lambda,类型系统中的异常规范
112 | * 更新了关于 C++14 和 C++17 中可变参数泛型 lambda 的部分
113 | * 更新了关于 C++11 和 C++20 中可变参数包的新部分
114 | * 如果可能的话,在更长的例子中使用 const 和 noexcept
115 | * 细节描述上的措词更正、全书目录结构布局的微调。
116 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: C++ Lambda Story
2 | site_url: https://nolongerwait.github.io/cpp_lambda_story_chinese_edition/
3 | site_author: nolongerwait
4 | site_description: >-
5 | C++ Lambda Story
6 |
7 | # Repository
8 | repo_name: cpp-lambda-story-chinese-edition
9 | repo_url: https://github.com/nolongerwait/cpp_lambda_story_chinese_edition
10 | edit_uri: ""
11 |
12 | # Copyright
13 | copyright: Copyright © 2022 nolongerwait
14 |
15 | # Configuration
16 | theme:
17 | name: material
18 | custom_dir: theme
19 |
20 | # Static files
21 | static_templates:
22 | - 404.html
23 |
24 | # Don't include MkDocs' JavaScript
25 | include_search_page: false
26 | search_index_only: true
27 |
28 | language: zh
29 |
30 | features:
31 | - header.autohide
32 | # - navigation.instant
33 | # - navigation.expand
34 | # - navigation.sections
35 | - navigation.tracking
36 | # - navigation.tabs
37 | # - navigation.tabs.sticky
38 | - navigation.top
39 | # - navigation.indexes
40 | - search.highlight
41 | - search.share
42 | - search.suggest
43 | - toc.integrate
44 | - content.code.annotate
45 |
46 | # insiders only
47 | # - content.tabs.link
48 |
49 | palette:
50 | - media: "(prefers-color-scheme: light)"
51 | scheme: default
52 | primary: light blue
53 | accent: deep purple
54 | toggle:
55 | icon: material/weather-sunny
56 | name: Switch to dark mode
57 | - media: "(prefers-color-scheme: dark)"
58 | scheme: dracula
59 | primary: deep purple
60 | accent: deep purple
61 | toggle:
62 | icon: material/weather-night
63 | name: Switch to light mode
64 |
65 | font:
66 | text: Roboto
67 | code: Roboto Mono
68 | favicon: assets/1200px-ISO_C++_Logo.svg.png
69 | icon:
70 | repo: fontawesome/brands/github
71 | logo: assets/1200px-ISO_C++_Logo.svg.png
72 |
73 | # Plugins
74 | plugins:
75 | - search:
76 | lang: ja
77 | - git-revision-date-localized:
78 | type: date
79 | enable_creation_date: true
80 | - minify:
81 | minify_html: true
82 | - mkdocs-material-extras:
83 |
84 | # Customization
85 | extra:
86 | generator: false
87 | analytics:
88 | provider: google
89 | property: !ENV GOOGLE_ANALYTICS_KEY
90 | social:
91 | - icon: fontawesome/brands/github
92 | link: https://github.com/nolongerwait/cpp_lambda_story_chinese_edition
93 |
94 | # Extensions
95 | markdown_extensions:
96 | - admonition:
97 | - abbr:
98 | - attr_list:
99 | - def_list:
100 | - footnotes:
101 | - md_in_html:
102 | - meta:
103 | - markdown.extensions.smarty:
104 | smart_quotes: false
105 | - markdown.extensions.tables:
106 | - markdown.extensions.toc:
107 | slugify: !!python/name:pymdownx.slugs.uslugify
108 | permalink: ""
109 | toc_depth: 3
110 | - pymdownx.arithmatex:
111 | - pymdownx.betterem:
112 | - pymdownx.caret:
113 | - pymdownx.critic:
114 | - pymdownx.keys:
115 | - pymdownx.tilde:
116 | - pymdownx.mark:
117 | - pymdownx.details:
118 | - pymdownx.emoji:
119 | emoji_index: !!python/name:materialx.emoji.twemoji
120 | emoji_generator: !!python/name:materialx.emoji.to_svg
121 | - pymdownx.highlight:
122 | linenums: true
123 | linenums_style: pymdownx-inline
124 | anchor_linenums: true
125 | - pymdownx.inlinehilite:
126 | custom_inline:
127 | - name: math
128 | class: arithmatex
129 | format: !!python/name:pymdownx.arithmatex.inline_mathjax_format
130 | - pymdownx.magiclink:
131 | repo_url_shortener: true
132 | repo_url_shorthand: true
133 | social_url_shorthand: true
134 | social_url_shortener: true
135 | normalize_issue_symbols: true
136 | - pymdownx.smartsymbols:
137 | - pymdownx.superfences:
138 | preserve_tabs: true
139 | custom_fences:
140 | - name: mermaid
141 | class: mermaid
142 | format: !!python/name:pymdownx.superfences.fence_code_format
143 | - name: math
144 | class: arithmatex
145 | format: !!python/name:pymdownx.arithmatex.fence_mathjax_format
146 | - pymdownx.tabbed:
147 | alternate_style: true
148 | - pymdownx.tasklist:
149 | custom_checkbox: true
150 | clickable_checkbox: false
151 | - pymdownx.escapeall:
152 | hardbreak: true
153 | nbsp: true
154 | - pymdownx.progressbar:
155 | - pymdownx.striphtml:
156 | - pymdownx.snippets:
157 | check_paths: true
158 | - pymdownx.saneheaders:
159 |
160 | docs_dir: Source
161 |
162 | nav:
163 | - Getting Started: index.md
164 | - 关于此书: Chapter0/README.md
165 | - Lambda in C++98/03: Chapter1/README.md
166 | - Lambda in C++11: Chapter2/README.md
167 | - Lambda in C++14: Chapter3/README.md
168 | - Lambda in C++17: Chapter4/README.md
169 | - Lambda in C++20: Chapter5/README.md
170 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # C++ Lambda Story - From C++98 to C++20
2 | ## 介绍
3 | 本文为《C++ Lambda Story》的中文翻译,如果您觉得此书有价值,可以在 [https://leanpub.com/cpplambda](https://leanpub.com/cpplambda) 上支持下原作者。
4 |
5 | 或者如果您认识相关的翻译工作者或者出版社,可以积极联系原作者与出版社进行正规的中文翻译并出版。
6 |
7 | ## 阅读
8 | [在线阅览](https://nolongerwait.com/cpp_lambda_story_chinese_edition/)
9 |
10 | ## 目录
11 |
12 | - [关于此书](Source/Chapter0/README.md)
13 | - [成书渊源](Source/Chapter0/README.md#成书渊源)
14 | - [阅读对象](Source/Chapter0/README.md#阅读对象)
15 | - [读者反馈](Source/Chapter0/README.md#读者反馈)
16 | - [代码证书](Source/Chapter0/README.md#代码证书)
17 | - [代码格式](Source/Chapter0/README.md#代码格式)
18 | - [语法高亮限制](Source/Chapter0/README.md#语法高亮限制)
19 | - [在线编译器](Source/Chapter0/README.md#在线编译器)
20 | - [关于作者](Source/Chapter0/README.md#关于作者)
21 | - [致谢](Source/Chapter0/README.md#致谢)
22 | - [校阅历史](Source/Chapter0/README.md#校阅历史)
23 | - 一、[Lambda in C++98/03](Source/Chapter1/README.md)
24 | - [C++98/03 中的可调用对象](Source/Chapter1/README.md#1-C++98/03-中的可调用对象)
25 | - [仿函数的一些问题](Source/Chapter1/README.md#2-仿函数的一些问题)
26 | - [使用辅助函数](Source/Chapter1/README.md#3-使用辅助函数)
27 | - [新特性的动机](Source/Chapter1/README.md#4-新特性的动机)
28 | - 二、[Lambda in C++11](Source/Chapter2/README.md)
29 | - [Lambda 表达式的语法](Source/Chapter2/README.md#1-Lambda-表达式的语法)
30 | - [Lambda 表达式的一些例子](Source/Chapter2/README.md#Lambda-表达式的一些例子)
31 | - [Lambda 在编译器的展开](Source/Chapter2/README.md#Lambda-在编译器的展开)
32 | - [Lambda 表达式的类型](Source/Chapter2/README.md#2-Lambda-表达式的类型)
33 | - [构造,还是拷贝?](Source/Chapter2/README.md#构造还是拷贝)
34 | - [调用操作符](Source/Chapter2/README.md#3-调用操作符)
35 | - [重载](Source/Chapter2/README.md#重载)
36 | - [其他修饰符](Source/Chapter2/README.md#其他修饰符)
37 | - [捕获](Source/Chapter2/README.md#4-捕获)
38 | - [`mutable` 关键字](Source/Chapter2/README.md#mutable-关键字)
39 | - [调用计数器 - 捕获变量的一个例子](Source/Chapter2/README.md#调用计数器---捕获变量的一个例子)
40 | - [捕获全局变量](Source/Chapter2/README.md#捕获全局变量)
41 | - [捕获静态变量](Source/Chapter2/README.md#捕获静态变量)
42 | - [捕获类成员和 `this` 指针](Source/Chapter2/README.md#捕获类成员和-this-指针)
43 | - [只能移动的对象](Source/Chapter2/README.md#只能移动的对象)
44 | - [保留常量](Source/Chapter2/README.md#保留常量)
45 | - [捕获参数包](Source/Chapter2/README.md#捕获参数包)
46 | - [返回类型](Source/Chapter2/README.md#5-返回类型)
47 | - [尾部返回类型语法](Source/Chapter2/README.md#尾部返回类型语法)
48 | - [转化为函数指针](Source/Chapter2/README.md#6-转化为函数指针)
49 | - [一个有趣的例子](Source/Chapter2/README.md#一个有趣的例子)
50 | - [IIFE - 立即调用函数表达式](Source/Chapter2/README.md#7-IIFE---立即调用函数表达式)
51 | - [可读性提示](Source/Chapter2/README.md#可读性提示)
52 | - [Lambda 继承](Source/Chapter2/README.md#8-Lambda-继承)
53 | - [在容器中存储 Lambda](Source/Chapter2/README.md#9-在容器中存储-Lambda)
54 | - [总结](Source/Chapter2/README.md#10.-总结)
55 | - 三、[Lambda in C++14](Source/Chapter3/README.md)
56 | - [为 Lambda 增加默认参数](Source/Chapter3/README.md#1.-为-Lambda-增加默认参数)
57 | - [返回类型](Source/Chapter3/README.md#2.-返回类型)
58 | - [带有初始化的捕获](Source/Chapter3/README.md#3-带有初始化的捕获)
59 | - [限制](Source/Chapter3/README.md#限制)
60 | - [对现有问题的改进](Source/Chapter3/README.md#对现有问题的改进)
61 | - [泛型 Lambda](Source/Chapter3/README.md#4-泛型-Lambda)
62 | - [可变泛型参数](Source/Chapter3/README.md#可变泛型参数)
63 | - [使用泛型 Lambda 进行完美转发](Source/Chapter3/README.md#使用泛型-Lambda-进行完美转发)
64 | - [减少一些隐蔽的类型纠正](Source/Chapter3/README.md#减少一些隐蔽的类型纠正)
65 | - [使用 Lambda 代替 std::bind1st 和 std::bind2nd](Source/Chapter3/README.md#5-使用-Lambda-代替-std::bind1st-和-std::bind2nd)
66 | - [使用现代 C++ 技术](Source/Chapter3/README.md#使用现代-C++-技术)
67 | - [函数组合](Source/Chapter3/README.md#函数组合)
68 | - [Lambda 提升(LIFTing with Lambda)](Source/Chapter3/README.md#6-Lambda-提升LIFTing-with-Lambda)
69 | - [递归 Lambda](Source/Chapter3/README.md#7-递归-Lambda)
70 | - [利用 std::function](Source/Chapter3/README.md#利用-std::function)
71 | - [内部 Lambda 和泛型参数](Source/Chapter3/README.md#内部-Lambda-和泛型参数)
72 | - [更多技巧](Source/Chapter3/README.md#更多技巧)
73 | - [使用递归 Lambda 是最好的选择吗?](Source/Chapter3/README.md#使用递归-Lambda-是最好的选择吗)
74 | - [总结](Source/Chapter3/README.md#8-总结)
75 | - 四、[Lambda in C++17](Source/Chapter4/README.md)
76 | - [Lambda 语法更新](Source/Chapter4/README.md#1-Lambda-语法更新)
77 | - [类型系统中的异常规范](Source/Chapter4/README.md#2-类型系统中的异常规范)
78 | - [constexpr Lambda 表达式](Source/Chapter4/README.md#3-constexpr-Lambda-表达式)
79 | - [用例](Source/Chapter4/README.md#用例)
80 | - [捕获变量](Source/Chapter4/README.md#捕获变量)
81 | - [constexpr 总结](Source/Chapter4/README.md#constexpr-总结)
82 | - [捕获 *this](Source/Chapter4/README.md#4-捕获-this)
83 | - [一些指导性意见](Source/Chapter4/README.md#一些指导性意见)
84 | - [IIFE 更新](Source/Chapter4/README.md#5-IIFE-更新)
85 | - [可变泛型 Lambda 的更新](Source/Chapter4/README.md#6-可变泛型-Lambda-的更新)
86 | - [从多个 Lambda 派生](Source/Chapter4/README.md#7-从多个-Lambda-派生)
87 | - [自定义模板参数推导规则](Source/Chapter4/README.md#自定义模板参数推导规则)
88 | - [聚合初始化的扩展](Source/Chapter4/README.md#聚合初始化的扩展)
89 | - [std::variant 和 std::visit 的例子](Source/Chapter4/README.md#std::variant-和-std::visit-的例子)
90 | - [使用 Lambda 进行并发编程](Source/Chapter4/README.md#8-使用-Lambda-进行并发编程)
91 | - [Lambda 和 std::thread](Source/Chapter4/README.md#Lambda-和-std::thread)
92 | - [Lambda 和 std::async](Source/Chapter4/README.md#Lambda-和-std::async)
93 | - [Lambda 和 C++17 的并行算法](Source/Chapter4/README.md#Lambda-和-C++17-的并行算法)
94 | - [Lambda 和异步 - 总结](Source/Chapter4/README.md#Lambda-和异步---总结)
95 | - [总结](Source/Chapter4/README.md#9-总结)
96 | - 五、[Lambda in C++20](Source/Chapter5/README.md)
97 | - [Lambda 语法更新](Source/Chapter5/README.md#1-Lambda-语法更新)
98 | - [更新快览](Source/Chapter5/README.md#2-更新快览)
99 | - [consteval Lambda](Source/Chapter5/README.md#3-consteval-Lambda)
100 | - [捕获参数包](Source/Chapter5/README.md#4-捕获参数包)
101 | - [模板 Lambda](Source/Chapter5/README.md#5-模板-Lambda)
102 | - [Concept 和 Lambda](Source/Chapter5/README.md#6-Concept-和-Lambda)
103 | - [无状态 Lambda 的变更](Source/Chapter5/README.md#7-无状态-Lambda-的变更)
104 | - [补充一些关于“未评估的 concept”](Source/Chapter5/README.md#补充一些关于未评估的-concept)
105 | - [Lambda 和 constexpr 算法](Source/Chapter5/README.md#8-Lambda-和-constexpr-算法)
106 | - [C++20 对重载模式的更新](Source/Chapter5/README.md#9-C++20-对重载模式的更新)
107 | - [总结](Source/Chapter5/README.md#10-总结)
108 | - 附录A - 技术名录
109 | - 附录B - 五大使用C++ Lambda的优势
110 | - 参考
111 | - 笔记
112 |
113 | ## 致谢
114 | 本书 Chapter 4 C++17 章节翻译感谢 [@Dup4](https://github.com/Dup4) 同学的支持。
115 | 在线阅览能力也感谢 [@Dup4](https://github.com/Dup4) 同学的支持。
116 |
--------------------------------------------------------------------------------
/Source/Chapter1/README.md:
--------------------------------------------------------------------------------
1 | # 一、Lambda in C++98/03
2 |
3 | 凡是在开始之前,对主题的背景做出一些介绍总是好的。
4 |
5 | 所以,我们首先会聊一聊在没有现代 C++ 之前的那些 C++ 代码。
6 |
7 | 在本章,你可以学到:
8 |
9 | * 如何从标准库传递一个仿函数给算法
10 | * 仿函数和函数指针的局限性
11 | * 为什么辅助函数不够好使
12 | * C++0x/C++11 中添加新特性的动机
13 |
14 | ## 1. C++98/03 中的可调用对象
15 |
16 | 首先来聊聊标准库中基本思想之一的算法,像 `std::sort`,`std::for_each`,`std::transform` 等,可以调用任何可调用对象以及调用输入容器中的一个元素。
17 |
18 | 然而,在 C++98/03 中,这些操作只包含指向函数的指针或者仿函数。
19 |
20 | 举一个例子,我们来看一看一个打印 `vector` 中全部元素的应用程序。
21 |
22 | 在第一版中,我们将使用规范的函数:
23 |
24 | > 代码 1-1 [基础输出函数](https://wandbox.org/permlink/XiMBBTOG122vplUS)
25 |
26 | ```c++
27 | #include
28 | #include
29 | #include
30 |
31 | void PrintFunc(int x) {
32 | std::cout << x < v;
37 | v.push_back(1);
38 | v.push_back(2);
39 | std::for_each(v.begin(), v.end(), PrintFunc);
40 | }
41 | ```
42 |
43 | 上面的代码使用了 `std::for_each` 来从 `vector` 中迭代每个元素(请注意此时的 C++ 为 98/03 版本,尚不支持范围式循环),同时传递了一个可调用对象 `PrintFunc`。
44 |
45 | 我们可以将这个简单的函数转化为一个仿函数:
46 |
47 | > 代码 1-2 [基础输出仿函数](https://wandbox.org/permlink/7OGJzJlfg40SSQUG)
48 |
49 | ```cpp
50 | #include
51 | #include
52 | #include
53 |
54 | struct PrintFunctor {
55 | void operator()(int x) const {
56 | std::cout << x < v;
62 | v.push_back(1);
63 | v.push_back(2);
64 | std::for_each(v.begin(), v.end(), PrintFunctor());
65 | }
66 | ```
67 |
68 | 本用例重载了操作符 `()` 来定义了一个简单的仿函数。
69 |
70 | 相较于通常无状态的函数指针,仿函数能够持有成员变量来允许存储状态。
71 |
72 | 一个例子:统计在算法中调用可调用对象的次数。
73 |
74 | 这需要在仿函数中存储一个计数器,并且在每次 lambda 调用时更新计数:
75 |
76 | > 代码 1-3 [带有状态的仿函数]()
77 |
78 | ```cpp
79 | #include
80 | #include
81 | #include
82 |
83 | struct PrintFunctor {
84 | PrintFunctor(): numCalls(0){}
85 |
86 | void operator()(int x) const {
87 | std::cout << x < v;
96 | v.push_back(1);
97 | v.push_back(2);
98 | const PrintFunctor visitor = std::for_each(v.begin(), v.end(), PrintFunctor());
99 | std::cout << "num calls: " << visitor.numCalls << '\n';
100 | }
101 | ```
102 |
103 | 在上面的例子中,我们使用了成员变量 `numCalls` 来统计调用操作符被调用的次数。
104 |
105 | 由于调用操作符是一个 `const` 成员函数,我使用了 `mutable` 类型的变量。
106 |
107 | 如您所料,我们得到的输出结果就是:
108 |
109 | ```plaintext
110 | 1
111 | 2
112 | num calls: 2
113 | ```
114 |
115 | 我们也可以从调用范围中「捕获」变量。
116 |
117 | 想要达到这个效果,我们需要在仿函数中创建一个成员变量并且在构造器中初始化它。
118 |
119 | > 代码 1-4 [带有“捕获”变量的仿函数](https://wandbox.org/permlink/ogenCfT7ZCTbRIkZ)
120 |
121 | ```cpp
122 | #include
123 | #include
124 | #include
125 | #include
126 |
127 | struct PrintFunctor {
128 | PrintFunctor(const std::string& str) : strText(str), numCalls(0) {}
129 | void operator()(int x) const {
130 | std::cout << strText << x << '\n';
131 | ++numCalls;
132 | }
133 | std::string strText;
134 | mutable int numCalls;
135 | };
136 |
137 | int main() {
138 | std::vector v;
139 | v.push_back(1);
140 | v.push_back(2);
141 | const std::string introText("Elem: ");
142 | const PrintFunctor visitor = std::for_each(v.begin(), v.end(), PrintFunctor(introText));
143 | std::cout << "num calls: " << visitor.numCalls << '\n';
144 | }
145 | ```
146 |
147 | 在这个版本中,`PrintFunctor` 使用了一个额外的参数来初始化成员变量。
148 |
149 | 然后这个变量在调用操作符中被使用。所以最终期望的输出是:
150 |
151 | ```plaintext
152 | Elem: 1
153 | Elem: 2
154 | num calls: 2
155 | ```
156 |
157 | ## 2. 仿函数的一些问题
158 |
159 | 如您所见,仿函数的功能很强大。他们由一个独立的类所表示,您可以根据您的需要来设计、改造并使用它。
160 |
161 | 然而 C++98/03 问题在于需要在不同的地方编写一个函数或者仿函数,而不是算法调用对象本身。
162 |
163 | 这意味着这段代码会在源文件的中占用几十到上百行,而且这样分离的写法并不利于日后代码的维护。
164 |
165 | 一个可行的解决办法,那就是再编写一个本地仿函数类,因为 C++ 支持这样的语法,但是这不意味着它能如预期一样工作。
166 |
167 | 来看看这段代码:
168 |
169 | > 代码 1-5 本地仿函数类
170 |
171 | ```cpp
172 | int main() {
173 | struct PrintFunctor {
174 | void operator()(int x) const {
175 | std::cout << x << std::endl;
176 | }
177 | };
178 | std::vector v(10, 1);
179 | std::for_each(v.begin(), v.end(), PrintFunctor());
180 | }
181 | ```
182 |
183 | 您可以用 GCC 来尝试编译它(带上 C++98 的标签 `-std=c++98`),当然不出意外,将会出现如下的编译错误:
184 |
185 | ```plaintext
186 | error: template argument for
187 | 'template _Funct
188 | std::for_each(_IIter, _IIter, _Funct)'
189 | uses local type 'main()::PrintFunctor'
190 | ```
191 |
192 | 在 C++98/03 中,你不能用本地类型来初始化一个模板。
193 |
194 | 当认识到并理解了这些限制产生的原因,C++ 开发者就可以在 C++98/03 中找到一种解决办法:使用一组辅助函数。
195 |
196 | ## 3. 使用辅助函数
197 |
198 | 使用一些辅助函数或者预定义好的仿函数会如何呢?
199 |
200 | 如果您查阅过标准库中 `` 头文件的源码,你会发现一些可在标准算法中被立即使用的类型或者函数。
201 |
202 | 例如:
203 |
204 | * `std::plus()`- 传入两个参数并返回他们的和
205 | * `std::minus()`- 传入两个参数并返回他们的差
206 | * `std::less()`- 传入两个参数并判断第一个参数是否小于第二个参数
207 | * `std::greater_equal()`- 传入两个参数并判断第一个参数是否大于等于第二个参数
208 | * `std::bind1st`- 用给定的第一个参数创建一个可调用对象
209 | * `std::bind2nd`- 用给定的第二个参数创建一个可调用对象
210 | * 等等
211 |
212 | 让我们编写一些充分利用这些辅助函数的代码:
213 |
214 | > 代码 1-6 [使用旧 C++98/03 的辅助函数](https://wandbox.org/permlink/9KgfRwwC3Dza2ZVh)
215 |
216 | ```cpp
217 | #include
218 | #include
219 | #include
220 |
221 | int main() {
222 | std::vector v;
223 | v.push_back(1);
224 | v.push_back(2);
225 | // .. push back until 9...
226 | const size_t smaller5 = std::count_if(v.begin(), v.end(), std::bind2nd(std::less(), 5));
227 | return smaller5;
228 | }
229 | ```
230 |
231 | 这个例子使用 `std::less` 并且用 `std::bind2nd` 来固定第二个参数,同时,这个整体又被传入了 `std::count_if`。
232 |
233 | 您可能会猜到,这个代码可以展开为一个用来简单判断大小关系的函数:`return x < 5;`
234 |
235 | 如果你准备好使用等多的辅助函数,您也可以看看 `boost` 库,例如 `boost::bind`。
236 |
237 | 不幸的是,最主要的问题是这种方式十分的复杂并且语法不易学习。
238 |
239 | 举个例子,使用更多的辅助函数将会导致代码变得很不自然。
240 |
241 | 来看看这个:
242 |
243 | > 代码 1-7 [组合使用辅助函数](https://wandbox.org/permlink/D7XjbyM0i2nslhRU)
244 |
245 | ```cpp
246 | using std::placeholders::_1;
247 | std::vector v;
248 | v.push_back(1);
249 | v.push_back(2);
250 | // .. push back until 9...
251 | const size_t val = std::count_if(v.begin(), v.end(),
252 | std::bind(
253 | std::logical_and(), std::bind(std::greater(), _1, 2), std::bind(std::less_equal(), _1, 6)));
254 | // _1 comes from the std::placeholder namespace
255 | ```
256 |
257 | 这个组合使用 `std::bind`(当然了 `std::bind` 是 C++11 的功能,而不是 C++98/03)并结合 `std::greater` 和 `std::less_equal` 甚至联系到 `std::logical_and`。
258 |
259 | 哦对,`_1` 是一个第一输入参数的占位符。
260 |
261 | 尽管上述代码有效,并且您可以在本地定义它,但您不得不忍痛它很复杂且语法不自然。
262 |
263 | 更何况这个组合只代表一个简单的条件:`return x > 2 && x <= 6;`。
264 |
265 | 有什么更好以及更自然的方式吗?
266 |
267 | ## 4. 新特性的动机
268 |
269 | 在 C++98/03 中,有很多方式来声明或者传递一个可调用对象给标准库的算法或者公用组件。
270 |
271 | 然而,所有的这些都一些限制。例如,你不能声明一个本地的仿函数对象,以及使用辅助函数组合起来的一个复杂表达。
272 |
273 | 幸运的是,在 C++11 中我们有了很多新的提升。
274 |
275 | 首先,C++ 委员会解除了使用本地类型的模板进行实例化的限制。
276 |
277 | 现在你可以在你需要的地方编写本地仿函数了。
278 |
279 | 还有,C++11 带来了另一个想法:如果编译器可以为开发人员“编写”小巧简洁的仿函数呢?
280 |
281 | 这意味着通过一些新语法,我们可以“就地”创建仿函数。
282 |
283 | C++ 从此开启了更简洁、更紧凑的语法的新篇章。
284 |
285 | 这就是 Lambda 表达式的诞生。
286 |
287 | 如果我们回头看看 [N3337](https://timsong-cpp.github.io/cppwp/n3337/) 草案——C++11 的最终草案,我们可以看到关于 lambda 的独立章节 [\[expr.prim.lambda\]](https://timsong-cpp.github.io/cppwp/n3337/expr.prim.lambda)
288 |
289 | 下个章节,我们将一起看看这个新的 C++ 特性。
290 |
--------------------------------------------------------------------------------
/Source/Chapter5/README.md:
--------------------------------------------------------------------------------
1 | # 五、Lambda in C++20
2 |
3 | 2020 年 2 月,在捷克首都布拉格的会议上,ISO 委员会最终通过 C++20 标准,并宣布其将于 2020 年末正式发布。
4 |
5 | 新的标准规范为 C++ 语言本身和标准库都带来了诸多显著性的提升和改进!Lambda 表达式也得到了一些更新。
6 |
7 | 本章中,主要关注下列内容:
8 |
9 | * C++20 中的变化
10 | * 新的选择 - 捕获 `this` 指针
11 | * 模板 Lambda
12 | * 如何通过 `concepts` 提高泛型 Lambda
13 | * 如何在 Lambda 中使用 `constexpr` 算法
14 | * 如何使 `overloaded` 模式更加简短
15 |
16 | 你可以在 [N4681](https://timsong-cpp.github.io/cppwp/n4861/) 中的 [\[expr.prim.lambda\]](https://timsong-cpp.github.io/cppwp/n4861/expr.prim.lambda) 章节查阅标准规范中 Lambda 相关的内容。
17 |
18 | ## 1. Lambda 语法更新
19 |
20 | 在 C++20 中,Lambda 的语法得到了改进:
21 |
22 | * 现在可以在参数列表后添加 `consteval` 关键字
23 | * 现在明确模板尾(template tail)是可选的
24 | * 现在在尾部返回后,可以添加 `requires` 声明
25 |
26 | ```cpp
27 | [] () specifiers exception attr -> ret requires { /*code; */ }
28 | ^ ^ ^ ^ ^
29 | | | | | |
30 | | | | | optional: trailing return type
31 | | | | |
32 | | | | optional: mutable, constexpr, consteval, noexcept, attributes
33 | | | |
34 | | | parameter list (optional when no specifiers added)
35 | | |
36 | | optional: template parameter list
37 | |
38 | lambda introducer with an optional capture list
39 | ```
40 |
41 | ## 2. 更新快览
42 |
43 | C++20 中 Lambda 表达式的相关特性:
44 |
45 | * 允许 `[=, this]` 作为 Lambda 捕获 -[P0409R2](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0409r2.html) 并且弃用了通过 `[=]` 隐式捕获 `this`-[P0806](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0806r2.html)
46 | * 初始化捕获中的包扩展:`[...args = std::move(args)](){}`-[P0780](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0780r2.html)
47 | * `static`,`thread_local` 和 Lambda 捕获的结构化绑定 -[P1091](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1091r3.html)
48 | * 模板 Lambda(带有 `concepts`)-[P0428R2](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0428r2.pdf)
49 | * 简化显式的 Lambda 捕获 -[P0588R1](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0588r1.html)
50 | * 默认可构造和可分配的无状态 Lambda -[P0624R2](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0624r2.pdf)
51 | * 未评估上下文的 Lambda -[P0315R4](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0315r4.pdf)
52 | * constexpr 算法 - 十分重要 [P0202](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0202r3.html),[P0879](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0879r0.html) 和 [P1645](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1645r1.html)
53 |
54 | 如果想了解更多 C++20 的内容,你可以阅读此篇比较 C++17 和 C++20 的文章:[Changes between C++17 and C++20](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2131r0.html)
55 |
56 | 当然你也可以阅读我关于 C++20 语言和标准库特性的的卡片笔记:[Bartek's coding blog: C++20 Reference Card](https://www.cppstories.com/2020/01/cpp20refcard.html/)
57 |
58 | 快速预览下这些新的改变:
59 |
60 | 新添加的功能“清理”了 Lambda 语法。同时,C++20 也增强了部分功能,允许我们在高级场景中使用 Lambda。
61 |
62 | 例如,根据 [P1091](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1091r3.html),我们可以捕获一个结构化绑定:
63 |
64 | > 代码 5-1 [在 Lambda 中捕获结构化绑定](https://wandbox.org/permlink/7d8oK3o5nP3wYbWB)
65 |
66 | ```cpp
67 | #include
68 | #include
69 |
70 | auto GetParams() {
71 | return std::tuple{std::string{"Hello World"}, 42};
72 | }
73 |
74 | int main() {
75 | auto [x, y] = GetParams();
76 | const auto ParamLength = [&x, &y]() {
77 | return x.length() + y;
78 | }();
79 | return ParamLength;
80 | }
81 | ```
82 |
83 | 一些编译器(如 GCC)甚至在 C++17 中就支持了捕获结构化绑定,即便当时的标准并未强制哟求。
84 |
85 | C++20 标准也有关于 `*this` 捕获的阐明。现在在方法中进行值捕获 `[=]` 会收到一条警告:
86 |
87 | > 代码 5-2 [隐式捕获 `*this` 的警告](https://wandbox.org/permlink/yRosU85B0Q9LnwOv)
88 |
89 | ```cpp
90 | struct Baz {
91 | auto foo() {
92 | return [=] { std::cout < 代码 5-3 [一个简单的即时 Lambda 函数](https://wandbox.org/permlink/3lFNMB080LBz2d1z)
117 |
118 | ```cpp
119 | int main() {
120 | const int x = 10;
121 | auto lam = [](int x) consteval {
122 | return x + x;
123 | };
124 | return lam(x);
125 | }
126 | ```
127 |
128 | 我们将新的关键字 `consteval` 放在了 Lambda 的参数列表之后,类似于 `constexpr` 的用法。严格的区别就在于,如果你将 `x` 的 `const` 移除,那么 `constexpr` Lambda 表达式仍旧可以在运行时工作,但是即时 Lambda 函数将无法成功编译。
129 |
130 | 默认情况下,如果 Lambda 函数体中遵循 `constexpr` 函数的规则,那么编译器会将调用操作符标记为隐式的 `constexpr`。
131 |
132 | 这并非 `consteval` 案例,因为它对类似这样的代码拥有更强的限制。
133 |
134 | 当然,这两个关键字无法同时使用。在草案 [P1073R3](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1073r3.html) 中你可以找到与此相关的全部描述。
135 |
136 | ## 4. 捕获参数包
137 |
138 | C++20 中还对 Lambda 中初始化捕获的包扩展带来了一个提升:
139 |
140 | ```cpp
141 | template
142 | void call(Args&& ... args) {
143 | auto ret = [...capturedArgs = std::move(args)](){};
144 | }
145 | ```
146 |
147 | 先前,在 C++20 之前,这段代码是无法通过编译的(参考 C++11 章节中 [这部分](../Chapter2/README.md#捕获参数包) 内容),为了解决这个问题,需要将参数打包进一个单独的元组中去。
148 |
149 | 关于捕获限制相关的历史内容,你可以参考 [P0780](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0780r2.html) 中的描述。
150 |
151 | 综上所述,我们可以使用在 C++11 章节中有关捕获一个可变参数包的例子并在 C++20 中新特性的加持下实践下。
152 |
153 | 看下面的例子,利用折叠表达式来打印每个被捕获的参数:
154 |
155 | > 代码 5-4 [捕获可变参数包](https://wandbox.org/permlink/8Bjc78jm2OpfcOcN)
156 |
157 | ```cpp
158 | #include
159 | #include
160 |
161 | template
162 | void captureTest(First&& first, Args&&... args) {
163 | const auto printer = [first = std::move(first), ... capturedArgs = std::move(args)] {
164 | std::cout << first;
165 | ((std::cout << ", " << capturedArgs), ...);
166 | std::cout << '\n';
167 | };
168 | printer();
169 | }
170 |
171 | int main() {
172 | auto ptr = std::make_unique(10);
173 | captureTest(std::move(ptr), 2, 3, 4);
174 | captureTest(std::move(ptr), 'a', 'b');
175 | }
176 | ```
177 |
178 | 输出:
179 |
180 | ```plaintext
181 | 0x1f0cb20, 2, 3, 4
182 | 0, a, b
183 | ```
184 |
185 | 在示例中,我们使用了一个 `printer` 对象,它很类似在 C++17 中写过的那样,但是在这儿我们用来捕获变量而不是作为转发 Lambda 参数使用。
186 |
187 | 代码中甚至传递了一个 `unique` 指针。我们传递了两次并且你可以看到在第二次调用时得到的结果为 `0`,因为此时指针已经丢失了它对那块内存块的所有权。
188 |
189 | ## 5. 模板 Lambda
190 |
191 | C++14 中就已经引入了泛型 Lambda,并且可以在模板中将参数类型也声明为 `auto` 类型。
192 |
193 | 例如:
194 |
195 | ```cpp
196 | [] (auto x) { x; };
197 | ```
198 |
199 | 编译器会生成一个调用操作符对应以下的模板方法:
200 |
201 | ```cpp
202 | template
203 | void operator ()(T x) { x; }
204 | ```
205 |
206 | 但是,这似乎没有办法去直接改变这个模板的参数,并且使用“真实”的模板参数。
207 |
208 | C++20 下,这都是可能的。
209 |
210 | 比如,如何限制 Lambda 仅对 `vector` 类型生效呢?
211 |
212 | 如下,有一个泛型 Lambda:
213 |
214 | ```cpp
215 | auto foo = [](auto& vec) {
216 | std::cout << std::size(vec) << '\n';
217 | std::cout << vec.capacity() << '\n';
218 | };
219 | ```
220 |
221 | 但是,如果你调用它并传入一个 `int` 参数(如 `foo(10)`),那你可能会遇到“晦涩难懂”的错误提示:
222 |
223 | ```cpp
224 | test.cc: In instantiation of
225 | 'main():: [with auto:1 = int]':
226 | test.cc:16:11: required from here
227 | test.cc:11:30: error: no matching function for call to 'size(const int&)'
228 | 11 | std::cout<< std::size(vec) << '\n';
229 | ```
230 |
231 | 在 C++20 中,可以这样写:
232 |
233 | ```cpp
234 | auto foo = [](std::vector const& vec) {
235 | std::cout << std::size(vec) << '\n';
236 | std::cout << vec.capacity() << '\n';
237 | };
238 | ```
239 |
240 | 它所对应的模板调用操作符为:
241 |
242 | ```cpp
243 |
244 | void operator()(std::vector const& s) { ... }
245 | ```
246 |
247 | 这样模板参数就在捕获子句 `[]` 之后了。
248 |
249 | 现在进行类似 `foo(10)` 的调用,那么会收到一个较人性化的消息:
250 |
251 | ```plaintext
252 | note: mismatched types 'const std::vector'and 'int'
253 | ```
254 |
255 | [上述例子](https://wandbox.org/permlink/gupbJfUfHHQ2y48q) 中,编译器会警告我们关于 Lambda 接口中的这个错误的匹配。
256 |
257 | 另外有一个重要的方面就是,在泛型 Lambda 的示例中,你只拥有一个变量而不是它的模板类型。
258 |
259 | 如果要访问类型,则需要使用 `decltype(x)`(对于带有 `auto x` 参数的 Lambda)。
260 |
261 | 这将会使得你的代码变得冗长。
262 |
263 | 例如(使用了 [P0428](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0428r2.pdf) 中的代码):
264 |
265 | > 代码 5-5 从泛型参数中推断
266 |
267 | ```cpp
268 | auto f = [](auto const& x) {
269 | using T = std::decay_t;
270 | T copy = x;
271 | T::static_function();
272 | using Iterator = typenameT::iterator;
273 | }
274 | ```
275 |
276 | 现在可以这样编写:
277 |
278 | > 代码 5-6 使用模板 Lambda
279 |
280 | ```cpp
281 | auto f = [](T const& x) {
282 | T copy = x;
283 | T::static_function();
284 | using Iterator = typenameT::iterator;
285 | }
286 | ```
287 |
288 | 和明显,在第一种写法中,我们不得不使用
289 |
290 | ```cpp
291 | using T = std::decay_t;
292 | ```
293 |
294 | 为了得到输入参数的类型,在 C++20 版本中,没有必要去访问模板参数了。
295 |
296 | 除此之外,还有一个重要的使用场景就是在可变泛型 Lambda 中进行完美转发:
297 |
298 | ```cpp
299 | // C++17
300 | auto ForwardToTestFunc = [](auto&&... args) {
301 | // what's the type of `args` ?
302 | return TestFunc(std::forward(args)...);
303 | };
304 | ```
305 |
306 | 每次你想要访问模板参数的类型是,你都需要去使用 decltype (),但是在模板 lambda 中就不需要了:
307 |
308 | ```cpp
309 | // C++20
310 | auto ForwardToTestFunc = [](T && ... args) {
311 | return TestFunc(std::forward(args)...); // we have allthe types!
312 | };
313 | ```
314 |
315 | 怎么样?模板 Lambda 提供了更为清晰的语法和更好的访问参数类型的途径。
316 |
317 | 当然,这还不够,你甚至也可以在 Lambda 使用 `concept`,咱们接着往下看。
318 |
319 | ## 6. Concept 和 Lambda
320 |
321 | `concept` 是编写模板的一项革命性进步。
322 |
323 | 它将允许你对模板参数进行约束,这可以极大提高代码的可读性,可能提升编译速度甚至能够提供更友善的错误信息。
324 |
325 | 话不多说,看个简单的示例吧:
326 |
327 | > 代码 5-7 一个普通的 `concept` 声明
328 |
329 | ```cpp
330 | // define a concept:
331 | template
332 | concept SignedIntegral = std::is_integral_v && std::is_signed_v;
333 | // use:
334 | template
335 | void signedIntsOnly(T val) {}
336 | ```
337 |
338 | 我们首先创建了一个 `concept` 描述类型为有符号的并且是整形。
339 |
340 | 请注意我们可以已有的类型特征。
341 |
342 | 之后,我们使用她来定义一个仅支持能匹配 `concept` 类型的模板函数。
343 |
344 | 在这我们没有使用 `typename T`,但是我们可以引用一个 `concept` 名字。
345 |
346 | 好了,简单了解了 `concept` 之后,那么怎么跟 Lambda 关联起来呢?
347 |
348 | 关键部分就在于精炼语法以及约束 `auto` 模板参数。
349 |
350 | ### 简化和精炼的语法
351 |
352 | 得益于 `concept` 精炼的语法特性,你也可以不用在编写模板时候带有 `template` 部分了。
353 |
354 | 使用无约束的 `auto`:
355 |
356 | ```cpp
357 | void myTemplateFunc (auto param) {}
358 | ```
359 |
360 | 使用有约束的 auto:
361 |
362 | ```cpp
363 | void signedIntsOnly (SignedIntegral auto val) {}
364 | void floatsOnly (std::floating_point auto fp) {}
365 | ```
366 |
367 | 这些语法跟在 C++14 中编写泛型 Lambda 时很像,当然,现在你可以这样做:
368 |
369 | ```cpp
370 | void myTemplateFunction (auto val) {}
371 | ```
372 |
373 | 换句话说,对于 lambda,我们可以利用它精炼的风格,例如对泛型 Lambda 参数添加额外的限制。
374 |
375 | ```cpp
376 | auto GenLambda = [](SignedIntegral auto param) { return param * param + 1; }
377 | ```
378 |
379 | 上面的例子利用 `SignedIntegral` 来限制 `auto` 参数。
380 |
381 | 但是整个表达式比起模板 Lambda 看上去更加的可读,这就是为什么我们要着重讨论的点了。
382 |
383 | 来一个有点难度的例子吧,我们甚至可以为一些类的接口定义 `concept`:
384 |
385 | > 代码 5-8 IRenderable concept, with requires keyword
386 |
387 | ```cpp
388 | template
389 | concept IRenderable = requires(T v) {
390 | { v.render() } -> std::same_as;
391 | { v.getVertCount() } -> std::convertible_to;
392 | };
393 | ```
394 |
395 | 上面这个例子定义了一个带有 render () 和 getVertCount () 成员函数,用来匹配全部类型的 concept。
396 |
397 | 使用它来写一个泛型 Lambda 试试:
398 |
399 | > 代码 5-9 [IRenderable concept/Interface 的实现](https://wandbox.org/permlink/5jLMVJIckSvDdgMv)
400 |
401 | ```cpp
402 | #include
403 | #include
404 |
405 | template
406 | concept IRenderable = requires(T v) {
407 | { v.render() } -> std::same_as;
408 | { v.getVertCount() } -> std::convertible_to;
409 | };
410 |
411 | struct Circle {
412 | void render() {
413 | std::cout << "drawing circle\n";
414 | }
415 | size_t getVertCount() const {
416 | return 10;
417 | };
418 | };
419 |
420 | struct Square {
421 | void render() {
422 | std::cout << "drawing square\n";
423 | }
424 | size_t getVertCount() const {
425 | return 4;
426 | };
427 | };
428 |
429 | int main() {
430 | const auto RenderCaller = [](IRenderable auto& obj) {
431 | obj.render();
432 | };
433 | Circle c;
434 | RenderCaller(c);
435 | Square s;
436 | RenderCaller(s);
437 | }
438 | ```
439 |
440 | 这个例子中 `RenderCaller` 就是一个泛型 `Lambda`,并且支持类型必须满足 `IRenderable concept`。
441 |
442 | ## 7. 无状态 Lambda 的变更
443 |
444 | 也许你会想起来 C++11 中我们提过的无状态、甚至没有默认构造化的 Lambda。
445 |
446 | 然而,这个限制在 C++20 中被解除了。
447 |
448 | 这就是为什么假如你的 Lambda 没有捕获任何东西的情况下,你也可以写下如下的代码:
449 |
450 | > 代码 5-10 [一个无状态 Lambda](https://wandbox.org/permlink/nWXXJiZyk8ZhVej9)
451 |
452 | ```cpp
453 | #include
454 | #include
455 | #include
456 |
457 | struct Product {
458 | std::string _name;
459 | int _id{0};
460 | double _price{0.0};
461 | };
462 |
463 | int main() {
464 | const auto nameCmp = [](const auto& a, const auto& b) {
465 | return a._name < b._name;
466 | };
467 | const std::set prodSet{
468 | {"Cup", 10, 100.0}, {"Book", 2, 200.5}, {"TV set", 1, 2000}, {"Pencil", 4, 10.5}};
469 | for (const auto& elem : prodSet)
470 | std::cout << elem._name << '\n';
471 | }
472 | ```
473 |
474 | 例子中我声明了一个集合用来存储一系列的产品。
475 |
476 | 同时我需要一个办法来比较这些产品,所以我传入了一个无状态的 Lambda 用来比较他们的产品名。
477 |
478 | 如果用 C++17 编译,那么你会收获如下关于使用了删除默认构造器的错误说明:
479 |
480 | ```plaintext
481 | test.h: In constructor
482 | 'std::set<_Key, _Compare, _Alloc>...
483 | [with _Key = Product;
484 | _Compare = main()::;
485 | ...'
486 | test.h:244:29: error: use of deleted function
487 | 'main()::::()'
488 | ```
489 |
490 | 但是在 C++20 中,你可以存储无状态 Lambda,甚至可以拷贝他们:
491 |
492 | > 代码 5-11 [存储无状态 Lambda](https://wandbox.org/permlink/wTMFVluKdDbsLyOK)
493 |
494 | ```cpp
495 | template
496 | struct Product {
497 | int _id{0};
498 | double _price{0.0};
499 | F _predicate;
500 | };
501 |
502 | int main() {
503 | const auto idCmp = [](const auto& a) noexcept {
504 | return a._id != 0;
505 | };
506 | Product p{10, 10.0, idCmp};
507 | [[maybe_unused]] auto p2 = p;
508 | }
509 | ```
510 |
511 | ### 补充一些关于“未评估的 concept”
512 |
513 | 还有一些与高级用例相关的变化,比如未评估的 `concept`。
514 |
515 | 连同默认的可构造 Lambda,您现在可以编写这样的代码:
516 |
517 | ```cpp
518 | std::mapy; })> map;
519 | ```
520 |
521 | 如您所见,现在可以在声明映射容器中指定 Lambda。它可以用作比较器仿函数。
522 |
523 | 这种“未评估 `concept`”对于高级模板元编程特别方便。
524 |
525 | 例如,在该功能的提案中,作者提到在编译时使用断言对元组对象进行排序,该断言是一个 Lambda。
526 |
527 | 更多的内容可以参考 [P0315R2](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0315r2.pdf)。
528 |
529 | ## 8. Lambda 和 `constexpr` 算法
530 |
531 | 回想一下之前章节中的内容,自 C++17 依赖,我们可以使用 `constexpr` Lambda。
532 |
533 | 并且,由于这项功能,我们可以传递 Lambda 给一个需要在编译器评估的函数。
534 |
535 | 在 C++20 中大多数标注算法都可以被关键字 `constexpr` 标记,这使得 `constexpr` Lambda 用起来更加方便了。
536 |
537 | 看一些例子吧还是。
538 |
539 | > 代码 5-12 [在普通的 constexpr Lambda 中使用 std::accumulate ()](https://godbolt.org/z/Tqkphs)
540 |
541 | ```cpp
542 | #include
543 | #include
544 | int main() {
545 | constexpr std::array arr{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
546 | // with constexpr lambda
547 | static_assert(std::accumulate(begin(arr), end(arr), 0, [](auto a, auto b) noexcept {
548 | return a + b;
549 | }) == 55);
550 | return arr[0];
551 | }
552 | ```
553 |
554 | 本例中,在 Lambda 中使用 `std::accumulate`,实际上使用的还是 `std::plus` 操作。
555 |
556 | 下个例子中,使用了一个带有 `cmp` 比较器 `cout_if` 算法的 `constexpr` 函数。
557 |
558 | > 代码 5-13 [给普通函数中传入一个 `constexpr` Lambda](https://godbolt.org/z/ouJ_4q)
559 |
560 | ```cpp
561 | #include
562 | #include
563 | constexpr auto CountValues(auto container, auto cmp) {
564 | return std::count_if(begin(container), end(container), cmp);
565 | }
566 | int main() {
567 | constexpr auto minVal = CountValues(std::array{-10, 6, 8, 4, -5, 2, 4, 6}, [](auto a) {
568 | return a >= 0;
569 | });
570 | return minVal;
571 | }
572 | ```
573 |
574 | > 哪些标准算法是可以 `constexpr` 的呢?
575 | > 所有 ``,`` 和 `` 头文件中的算法现在都可以被关键字 `constexpr` 标记。除了 `shuffle`,`sample`,`stable_sort`,`stable_partition`,`inplace_merge` 这些,以及接受执行策略参数的函数或重载函数。
576 | > 具体的内容可以查阅 [P0202](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0202r3.html),[P0879](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0879r0.html) 和 [P1645](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1645r1.html)。
577 |
578 | ## 9. C++20 对重载模式的更新
579 |
580 | 在前一章中,学习过如何从多个 Lambda 表达式派生并通过重载模式暴露它们。
581 |
582 | 这种技术对于 `std::variant` 访问很方便。
583 |
584 | 得益于 C++20 中类模板参数推断(CTAD,Class Template Argument Deduction)的更新,现在可以用更简短的语法来实现了。
585 |
586 | 为什么?
587 |
588 | 这是因为在 C++20 中有 CTAD 的扩展并且会自动处理聚合。
589 |
590 | 这意味着无需编写自定义的推断。
591 |
592 | 来一个简单的例子:
593 |
594 | ```cpp
595 | template
596 | struct Triple { T t; U u; V v; };
597 | ```
598 |
599 | 在 C++20 中的写法:
600 |
601 | ```cpp
602 | Triple ttt { 10.0f, 90, std::string{"hello"}};
603 | ```
604 |
605 | `T` 将被自动推断为 `float`,`U` 为 `int`,`V` 为 `std::string`。
606 |
607 | C++20 中的重载模式:
608 |
609 | ```cpp
610 | template struct overload: Ts... { using Ts:: operator()...; };
611 | ```
612 |
613 | 这个特性的草案可以在 [P1021](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1021r5.html) 和 [P1816](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1816r0.pdf) 中查阅。
614 |
615 | > GCC10 似乎实现了这个提议,但是它不适用于继承的高级案例。因此我们需要等待 GCC 对该特性进行完整的支持。
616 |
617 | ## 10. 总结
618 |
619 | 在本章中,我们回顾了 C++20 带来的变化。
620 |
621 | 首先,一些澄清和改进:例如捕获 `this`、捕获结构化绑定或默认构造无状态 Lambda 的能力。
622 |
623 | 更重要的是,还有更多重要的补充!
624 |
625 | 现在突出的功能之一是模板 Lambdas 和概念。
626 |
627 | 这样您就可以更好地控制通用 Lambdas。
628 |
629 | 总而言之,使用 C++20 及其所有功能,使得 Lambda 愈发成为更强大的工具!
630 |
--------------------------------------------------------------------------------
/Source/Chapter3/README.md:
--------------------------------------------------------------------------------
1 | # 三、Lambda in C++14
2 |
3 | C++14 为 Lambda 表达式提供了两个显著的增强特性
4 |
5 | * 带有初始化的捕获
6 | * 泛型 Lambda
7 | 此外,该标准还更新了一些规则,例如:
8 | * Lambda 表达式的默认参数
9 | * `auto` 返回类型
10 |
11 | 这些新增特性可以在 [N4140](https://timsong-cpp.github.io/cppwp/n4140/) 中的 Lambda 部分 [\[expr.prim.lambda\]](https://timsong-cpp.github.io/cppwp/n4140/expr.prim.lambda) 找到。
12 |
13 | 在本章中,你将学到:
14 |
15 | * 捕获成员变量
16 | * 用现代 C++ 技术代替旧功能,如 `std::bind1st`
17 | * LIFTING
18 | * 递归 Lambda
19 |
20 | ## 1. 为 Lambda 增加默认参数
21 |
22 | 让我们从小的变化说起吧:
23 |
24 | 在 C++14 中,你可以在 Lambda 调用中使用默认参数了。这一小小的更新让 Lambda 函数更像一个常规函数了。
25 |
26 | > 代码 3-1 [带有默认参数的 Lambda](https://wandbox.org/permlink/T2u5iuGqi3fHaN9q)
27 |
28 | ```cpp
29 | #include
30 |
31 | int main() {
32 | const auto lam = [](int x = 10) {
33 | std::cout << x << '\n';
34 | };
35 | lam();
36 | lam(100);
37 | }
38 | ```
39 |
40 | 见用例所示,我们可以调用这个 Lambda 两次:第一次不携带任何参数,结果将输出默认的 `10`,第二次我们传递参数 `100` 进去,结果会输出 `100`。
41 |
42 | 不过,这一特性早已在 GCC 和 Clang 的 C++11 版本中被支持了。
43 |
44 | ## 2. 返回类型
45 |
46 | 如果你还记得之前章节的内容,那么你一定知道,对于一个简单的 Lambda,编译器可以推断出它的返回类型。
47 |
48 | 这个功能是在常规函数上“扩展”的,在 C++14 中你可以使用 `auto` 作为返回类型
49 |
50 | ```cpp
51 | auto myFunction() {
52 | int x =computeX(...);
53 | int y =computeY(...);
54 | return x +y;
55 | }
56 | ```
57 |
58 | 如上,编译器会推断返回类型为 `int`。
59 |
60 | 推断返回类型的这部分内容在 C++14 中得到了改善和扩展。对于 Lambda 表达式来说,这意味着他们可以和常规函数享有同样的 `auto` 返回类型([\[expr.prim.lambda\]](https://timsong-cpp.github.io/cppwp/n4140/expr.prim.lambda#4)):
61 |
62 | > 如果 Lambda 返回类型是 `auto`,那么它会被尾部返回类型所替代(如果提供了)或者从 `return` 语句中推导。详见 [\[dcl.spec.auto\]](https://timsong-cpp.github.io/cppwp/n4140/dcl.spec.auto)
63 |
64 | 如果在 Lambda 中有多条返回语句,他们必须能够推断出同样的类型:
65 |
66 | ```cpp
67 | auto foo = [](int x){
68 | if (x < 0)
69 | return x * 1.1f
70 | else
71 | return x * 2.1
72 | }
73 | ```
74 |
75 | 这段代码就无法成功编译了,因为第一条返回语句返回 `float` 类型但第二条返回 `double` 类型。
76 |
77 | 编译器无法决定出到底应该将返回类型定为哪个,所以您必须选择其中一个,保证返回类型的唯一性。
78 |
79 | 尽管推断整形和双精度型也是很有用的,但是推断返回类型之所以有更显著的价值,是因为它可以在模板代码这种“未知”领域发挥极大地在作用。
80 |
81 | 举个例子,Lambda 闭包类型是匿名的,并且我们无法显式的明确它。
82 |
83 | 但是如果你想从函数中返回一个 Lambda 呢?你要如何明明确这个类型?
84 |
85 | 在 C++14 之前,你可以用 `std::function`:
86 |
87 | > 代码 3-2 [返回 `std::function`](https://wandbox.org/permlink/oCij1KoIB8RVOvSI)
88 |
89 | ```cpp
90 | #include
91 | #include
92 |
93 | std::function CreateMulLambda(int x) {
94 | return [x](int param) noexcept {
95 | return x * param;
96 | };
97 | }
98 |
99 | int main() {
100 | const auto lam = CreateMulLambda(10);
101 | std::cout << sizeof(lam);
102 | return lam(2);
103 | }
104 | ```
105 |
106 | 然而,上面这种方法并不足够直接。它要求你明确了一个函数签名,甚至包含了额外的头文件 ``。如果你还记得 C++11 的内容的话,`std::function` 是一个“笨重”的对象(在 GCC9 中,`function` 的 `sizeof` 是 32 bytes)。并且,它需要一些高级的内部机制,以便它可以处理任何可调用的对象。
107 |
108 | 感谢 C++14 带来的改进,我们可以极大的简化上面的代码:
109 |
110 | > 代码 3-3 [Lambda 推断的 `auto` 返回类型](https://wandbox.org/permlink/RLEHfrCk29aqRn8X)
111 |
112 | ```cpp
113 | #include
114 |
115 | auto CreateMulLambda(int x) noexcept {
116 | return [x](int param) noexcept {
117 | return x * param;
118 | };
119 | }
120 |
121 | int main() {
122 | const auto lam = CreateMulLambda(10);
123 | std::cout << sizeof(lam);
124 | return lam(2);
125 | }
126 | ```
127 |
128 | 现在我们就可以完全依靠编译时的类型推导,不需要其他辅助类型。
129 |
130 | 在 GCC 上,最后 `lam` 这个返回的 Lambda 对象的大小仅为 4 字节,并且比使用 `std::function` 的解决方案便宜得多。
131 |
132 | 这里有一点需要注意,我们也可以将 `CreateMulLambda` 标记为 `noexcept`,这样无论如何它都不可以抛出任何异常。
133 |
134 | 但是 `std::function` 就不行了。
135 |
136 | ## 3. 带有初始化的捕获
137 |
138 | 现在我们来讲讲更加具有建设性的更新。
139 |
140 | 你一定记得,在 Lambda 表达式中,你可以从外部范围中捕获变量。
141 |
142 | 编译器会拓展你的捕获语法并且在闭包类型中创建成员变量(非静态数据成员)。
143 |
144 | 现在在 C++14 中,你可以创建一个新的成员变量并且在捕获语句中初始化他们。
145 |
146 | 这样你就可以在 Lambda 内部访问那些变量了。
147 |
148 | 这叫做 **通过初始化器捕获** 或者你也可以用另一个名字 **广义 Lambda 捕获**。
149 |
150 | 看个简单的例子:
151 |
152 | > 代码 3-4 [通过初始化器捕获](https://wandbox.org/permlink/461XKCYNsQSKQeKO)
153 |
154 | ```cpp
155 | #include
156 |
157 | int main() {
158 | int x = 30;
159 | int y = 12;
160 | const auto foo = [z = x + y]() {
161 | std::cout << z << '\n';
162 | };
163 |
164 | x = 0;
165 | y = 0;
166 | foo();
167 | }
168 | ```
169 |
170 | 输出为
171 |
172 | ```plaintext
173 | 42
174 | ```
175 |
176 | 在这个例子中,编译会生成一个新的成员变量并且将其初始化为 `x + y`。
177 |
178 | 这个新变量的类型会被自动推断出来,即便你在变量前加上了 `auto` 关键字:
179 |
180 | ```cpp
181 | auto z = x + y
182 | ```
183 |
184 | 总之,前面示例中的 Lambda 会被解析为以下(简化的)仿函数:
185 |
186 | ```cpp
187 | struct _unnamedLambda {
188 | void operator()() const{
189 | std::cout << z << '\n';
190 | }
191 |
192 | int z;
193 | } someInstance;
194 | ```
195 |
196 | 当 Lambda 的表达式定义完成时,`z` 将会被直接初始化 `x + y`。
197 |
198 | 上面这句的含义就是:新变量在你定义 Lambda 的地方初始化,而不是你调用它的地方。
199 |
200 | 这就是为什么如果你在创建 Lambda 后修改 `x` 或者 `y` 变量,变量 `z` 的值不会改变。
201 |
202 | 在示例中,你可以看到在定义 Lambda 之后,我立即更改了 `x` 和 `y` 的值。
203 |
204 | 然而,输出仍将是 42,因为 `z` 在这之前就已经被初始化。
205 |
206 | 当然,通过初始化器创建变量也可以是灵活的,不妨看看下面这个例子:创建一个外部范围的引用变量。
207 |
208 | > 代码 3-5 [通过初始化器进行引用捕获](https://wandbox.org/permlink/TVb2allLLdRQ1aPe)
209 |
210 | ```cpp
211 | #include
212 |
213 | int main() {
214 | int x = 30;
215 | const auto foo = [&z = x]() {
216 | std::cout << z << '\n';
217 | };
218 | foo();
219 | x = 0;
220 | foo();
221 | }
222 | ```
223 |
224 | 这次,变量 `z` 是引用自变量 `x`,当然你也可以写成这样 `auto & z = x`。
225 |
226 | 如果运行这段代码,你应该可以看到,第一行会输出 30,但是第二行会输出 `0`。
227 |
228 | 这是因为我们进行了一个引用捕获,当你修改了引用内容时,对象 `z` 自然也会随之变化。
229 |
230 | ### 限制
231 |
232 | 需要注意,在使用初始化器捕获时,有一些限制:
233 |
234 | 一个是,当你通过初始化器进行引用捕获时,她不可能写入一个右值引用 `&&`。这是因为如下的代码目前是非法的:
235 |
236 | ```cpp
237 | [&& z = x] //非法语法
238 | ```
239 |
240 | 另一个该特性的限制是,它不允许传入参数包。在条款 [\[expr.prim.lambda\]](https://timsong-cpp.github.io/cppwp/n4140/expr.prim.lambda#24) 的 24 节可以阅读到如下内容:
241 |
242 | 带有省略号的简单捕获是包扩展([\[temp.variadic\]](https://timsong-cpp.github.io/cppwp/n4140/temp.variadic)),但是 init-capture 带有省略号是格式错误。
243 |
244 | 简而言之,在 C++14 中,你并不能这样写代码:
245 |
246 | ```cpp
247 | template < class.. Args >
248 | auto captureTest(Args... args) {
249 | return lambda = [...capturedArgs = std::move(args)](){};
250 | ...
251 | ```
252 |
253 | 但是,这个语法,在 C++20 中是支持的,如果想提前了解,可以参考 [这个]()。
254 |
255 | ### 对现有问题的改进
256 |
257 | 总而言之,这个新的 C++14 特性可以解决一些问题,例如 仅可移动类型 或 允许一些额外的优化。
258 |
259 | #### Move 移动
260 |
261 | 在 C++11 中,你无法通过值捕获的方式捕获一个唯一指针(`unique_pointer`),只能进行引用捕获。但是现在在 C++14 中,我们可以移动一个对象到闭包类型的成员中:
262 |
263 | > 代码 3-6 [捕获一个仅可移动类型](https://wandbox.org/permlink/n65fzPHrNnyDqbIK)
264 |
265 | ```cpp
266 | #include
267 | #include
268 |
269 | int main() {
270 | std::unique_ptr p(new int{10});
271 | const auto bar = [ptr = std::move(p)] {
272 | std::cout << "pointer in lambda: " << ptr.get() << '\n';
273 | };
274 | std::cout << "pointer in main(): " << p.get() << '\n';
275 | bar();
276 | }
277 | ```
278 |
279 | 输出:
280 |
281 | ```plaintext
282 | pointer in main(): 0
283 | pointer in lambda: 0x1413c20
284 | ```
285 |
286 | 有了捕获初始化器,你就可以移动一个指针的所有权到 Lambda 中。如你所见,在上面这个例子中,唯一指针在闭包对象被创建后立即被设为了 `nullptr`。
287 |
288 | 但是当你调用这个 Lambda 时,你会看见一个合法的内存地址。
289 |
290 | #### `std::function` 中的陷阱
291 |
292 | 在 lambda 中拥有一个仅可移动的捕获变量会让闭包对象变得不能被拷贝。
293 |
294 | 当你想在 `std::function` 中存储一个 Lambda,而这个 Lambda 接受仅可拷贝的可调用对象的时候,就会出现问题。
295 |
296 | 我们在 C++ Insights 上观察一下之前的一个例子([在线预览](https://cppinsights.io/s/5d11eb8f)),你会发现 `std::unique_ptr` 是一个闭包类型的成员变量。
297 |
298 | 但是,拥有一个仅可移动的成员会阻止编译器创建一个默认拷贝构造的。
299 |
300 | 简而言之,这段代码无法编译:
301 |
302 | > 代码 3-7 `std::function` 和 `std::move`
303 |
304 | ```cpp
305 | std::unique_ptr p(new int{10});
306 | std::function fn = [ptr = std::move(p)](){}; //不可编译
307 | ```
308 |
309 | 如果您想要完整的细节,您还可以查看草案([P0288]())中的 any\_invokable,这是 `std::function` 未来可能的改进,并且还会处理仅可移动类型。
310 |
311 | #### 优化 Optimisation
312 |
313 | 有一个将捕获初始化器作为潜在的性能优化的点子:我们可以在初始化器中计算一次,而不是每次调用 Lambda 时都计算某个值:
314 |
315 | > 代码 3-8 [给 Lambda 创建一个 `string`](https://wandbox.org/permlink/GWcJNoUsBFnscOp3)
316 |
317 | ```cpp
318 | #include
319 | #include
320 | #include
321 | #include
322 |
323 | int main() {
324 | using namespace std::string_literals;
325 | const std::vector vs = {"apple", "orange", "foobar", "lemon"};
326 | const auto prefix = "foo"s;
327 | auto result = std::find_if(vs.begin(), vs.end(), [&prefix](const std::string& s) {
328 | return s == prefix + "bar"s;
329 | });
330 | if (result != vs.end())
331 | std::cout << prefix << "-something found!\n";
332 | result = std::find_if(vs.begin(), vs.end(), [savedString = prefix + "bar"s](const std::string& s) {
333 | return s == savedString;
334 | });
335 | if (result != vs.end())
336 | std::cout << prefix << "-something found!\n";
337 | }
338 | ```
339 |
340 | 上面的代码对 `std::find_if` 调用了两次。在第一个场景中,我们捕获 `prefix` 并将输入值与 `prefix + "bar"s` 进行比较。
341 |
342 | 每次调用 Lambda 时,都必须创建并计算一个临时值来存储这些字符串的总和。
343 |
344 | 第二次调用 `find_if` 优化:我们创建了一个捕获的变量 `savedString` 来计算字符串的总和。
345 |
346 | 然后,我们可以安全地在 Lambda 体中引用它。
347 |
348 | 字符串的总和只会运行一次,而不是每次调用 lambda 时都会运行。
349 |
350 | 该示例还使用了 `std::string_literals`,这就是为什么我们可以编写代表 `std::string` 对象的 `"foo"s`。
351 |
352 | #### 捕获成员变量
353 |
354 | 初始化器也被用来捕获成员变量。我们可以捕获一个成员变量的拷贝并且不用担心悬空引用。
355 |
356 | 看个例子吧:
357 |
358 | > 代码 3-9 [捕获一个成员变量](https://wandbox.org/permlink/E65tipdkDj2nrdF5)
359 |
360 | ```cpp
361 | #include
362 | #include
363 |
364 | struct Baz {
365 | auto foo() const {
366 | return [s = s] {
367 | std::cout << s << std::endl;
368 | };
369 | }
370 |
371 | std::string s;
372 | };
373 |
374 | int main() {
375 | const auto f1 = Baz{"abc"}.foo();
376 | const auto f2 = Baz{"xyz"}.foo();
377 | f1();
378 | f2();
379 | }
380 | ```
381 |
382 | 在 `foo()` 中我们通过拷贝的方式将成员变量拷贝进了闭包类型中。
383 |
384 | 此外,我们使用 `auto` 来进行成员函数 `foo()` 返回类型的推断。
385 |
386 | 当然,在 C++11 中,你也可以使用 `std::function`,详见 [捕获成员变量和 `this` 指针](../Chapter2/README.md#捕获类成员和-`this`-指针)。
387 |
388 | 在这里我们在 lambda 中使用了一个很“奇怪”的语法 `[ s = s ]`,这段代码能够工作的原因是捕获到的变量是在闭包类型内部的,而非外部。所以这里就没有歧义冲突了。
389 |
390 | ## 4. 泛型 Lambda
391 |
392 | 这是 C++14 中有关 Lambda 的最大的更新!
393 |
394 | Lambda 的早期规范允许我们创建匿名函数对象并将它们传递给标准库中的各种泛型算法。
395 |
396 | 然而,闭包本身并不是“泛型”的。例如,您不能将模板参数指定为 Lambda 的参数。
397 |
398 | 当然,在 C++14 中,标准引入了 **泛型 Lambda** 现在我们可以这样写:
399 |
400 | ```cpp
401 | const auto foo = [](auto x, int y) {
402 | std::cout << x << ", " << y << '\n';
403 | };
404 |
405 | foo(10, 1);
406 | foo(10.1234, 2);
407 | foo("hello world", 3);
408 | ```
409 |
410 | 注意 Lambda 的参数 `auto x`,它等同于在闭包类型中使用一个模板声明:
411 |
412 | ```cpp
413 | struct {
414 | template < typename T >
415 | void operator ()(T x, int y) const {
416 | std::cout << x << ", " << y << '\n';
417 | }
418 | } someInstance
419 | ```
420 |
421 | 当然,当有多个 `auto` 参数时,代码将被扩展为多个模板参数:
422 |
423 | ```cpp
424 | const auto fooDouble =[](auto x, auto y) { /*...*/};
425 | ```
426 |
427 | 扩展为:
428 |
429 | ```cpp
430 | struct{
431 | template< typename T, typename U>
432 | void operator()(T x, U y) const{ /*...*/}
433 | } someOtherInstance;
434 | ```
435 |
436 | ### 可变泛型参数
437 |
438 | 但是这并不是全部,如果你需要更更多的函数参数类型,你可以自己进行可变性改造。
439 |
440 | 看这个栗子:
441 |
442 | > 代码 3-10 [用于求和的可变泛型 Lambda](https://wandbox.org/permlink/EVw677hLJwKpSpPg)
443 |
444 | ```cpp
445 | #include
446 |
447 | template
448 | auto sum(T x) {
449 | return x;
450 | }
451 |
452 | template
453 | auto sum(T1 s, T... ts) {
454 | return s + sum(ts...);
455 | }
456 |
457 | int main() {
458 | const auto sumLambda = [](auto... args) {
459 | std::cout << "sum of: " << sizeof...(args) << " numbers\n";
460 | return sum(args...);
461 | };
462 | std::cout << sumLambda(1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9);
463 | }
464 | ```
465 |
466 | 这段泛型 Lambda 代码中使用了 `auto ...` 来代表一个可变长参数包。理论上,它将在调用操作符中被展开为:
467 |
468 | ```cpp
469 | struct __anoymousLambda{
470 | template < typename ... T >
471 | void operator()(T... args) const {/*...*/}
472 | };
473 | ```
474 |
475 | 在 C++17 中,我们有了新的选择 [折叠表达式](),它可以改进泛型可变参数 Lambdas,而在 C++20 中,我们将获得对模板参数的更多控制。
476 |
477 | 有关更多信息,请参阅 C++17 对可变参数泛型 Lambdas 的更新以及 C++20 中关于 [模板 Lambda]() 的信息
478 |
479 | ### 使用泛型 Lambda 进行完美转发
480 |
481 | 使用泛型 Lambda 表达式,其实并不限定在只使用 `auto x`,您可以像其他 `auto` 变量一样添加任何限定符,如 `auto&`、`const auto&` 或 `auto&&`。
482 |
483 | 有一个十分便利的点是,你可以指定 `auto&& x` 使其成为转发(泛型)引用。这使您可以完美地转发输入参数:
484 |
485 | > 代码 3-11 [泛型 Lambda 进行完美转发](https://wandbox.org/permlink/kA2GNHFiLOGDu9d9)
486 |
487 | ```cpp
488 | #include
489 | #include
490 |
491 | void foo(const std::string&) {
492 | std::cout << "foo(const string&)\n";
493 | }
494 |
495 | void foo(std::string&&) {
496 | std::cout << "foo(string&&)\n";
497 | }
498 |
499 | int main() {
500 | const auto callFoo = [](auto&& str) {
501 | std::cout << "Calling foo() on: " << str << '\n';
502 | foo(std::forward(str));
503 | };
504 | const std::string str = "Hello World";
505 | callFoo(str);
506 | callFoo("Hello World Ref Ref");
507 | }
508 | ```
509 |
510 | 输出
511 |
512 | ```plaintext
513 | Calling foo() on: Hello World
514 | foo(const string&)
515 | Calling foo() on: Hello World Ref Ref
516 | foo(string&&)
517 | ```
518 |
519 | 示例代码定义了两个函数重载 `foo` 用于对 `std::string` 的 `const` 引用,另一个用于对 `std::string` 的右值引用。
520 |
521 | `callFoolambda` 使用泛型参数作为泛型引用([引用资料 6](https://isocpp.org/blog/2014/09/noexcept-optimization))。
522 |
523 | 如果您想将此 Lambda 重写为常规函数模板,它可能如下所示:
524 |
525 | ```cpp
526 | template
527 | void callFooFunc(T&& str) {
528 | std::cout << "Calling foo() on: " << str << '\n';
529 | foo(std::forward(str));
530 | }
531 | ```
532 |
533 | 如你所见,在泛型 Lambda 的加持下,在编写本地匿名函数时候,你现在有更多的选择了。
534 |
535 | 但是,这还不是全部。
536 |
537 | ### 减少一些隐蔽的类型纠正
538 |
539 | 泛型 Lambda 在发现类型推断有问题时,很有帮助。
540 |
541 | 来看个例子:
542 |
543 | > 代码 3-13 [对 `std::map` 的迭代器进行类型纠正](https://wandbox.org/permlink/pSbtIA2lgYa6r1bW)
544 |
545 | ```cpp
546 | #include
547 | #include
548 | #include