Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

现代 C++ / Modern C++

1. 现代 C++ 总览:不是特性清单,而是表达能力 / Modern C++ Overview

2. 移动、转发与值类别:std::move 为什么不移动 / Move, Forward, and Value Categories

3. 类型推导、初始化与 constexpr / Type Deduction, Initialization, and constexpr

4. 模板、实例化与 SFINAE / Templates, Instantiation, and SFINAE

5. concept 与约束:把模板要求写在接口上 / Concepts and Constraints

6. 异常、RAII 与 noexcept:失败路径也要清理 / Exceptions, RAII, and noexcept

7. lambda、捕获与生命周期:回调不是免费午餐 / Lambdas, Captures, and Lifetime

8. callable wrapper 与 std::invoke:怎样保存和调用行为 / Callable Wrappers and std::invoke

9. 词汇类型与错误建模:让接口自己说话 / Vocabulary Types and Error Modeling

10. 协程与异步抽象:先理解状态机 / Coroutines and Async Abstractions

本页目录

类型推导、初始化与 constexpr / Type Deduction, Initialization, and constexpr ​

auto 不是“类型随便写”。

它是让编译器根据初始化表达式,按一套明确规则推导类型。你不用亲手写出类型,但仍然要知道类型变成了什么。

1. 为什么现代 C++ 需要类型推导 ​

C++ 的类型有时很长。

比如:

cpp
#include <string>
#include <vector>

std::vector<std::string> names{"Alice", "Bob"};
std::vector<std::string>::iterator it = names.begin();
1
2
3
4
5

it 的类型又长又重复。

写成:

cpp
auto it = names.begin();
1

可读性反而更好。

这里 auto 的意义不是“我不关心类型”。

而是:

text
初始化表达式 names.begin() 已经清楚决定了类型。
把这个类型再手写一遍,噪声大于收益。
1
2

但如果你不知道 auto 的规则,它也会制造误解。

2. 普通 auto 会复制式推导 ​

看代码:

cpp
int x = 42;
const int cx = x;
const int& rx = x;

auto a = cx;
auto b = rx;
1
2
3
4
5
6

a 和 b 的类型都是 int。

这经常让初学者惊讶:

text
cx 是 const int,为什么 a 不是 const int?
rx 是 const int&,为什么 b 不是引用?
1
2

因为普通 auto 在这种写法下更像“拿表达式的值初始化一个新对象”。

它会丢掉顶层 const 和引用。

text
const int cx = 42
  |
  v
auto a = cx
  |
  v
a 是一个新的 int 对象
1
2
3
4
5
6
7

a 是新对象。

修改 a 不会修改 cx。

3. auto& 和 const auto& ​

如果你想保留引用关系,要写出来:

cpp
int x = 42;
const int cx = x;

auto& r1 = x;        // int&
const auto& r2 = x;  // const int&
auto& r3 = cx;       // const int&
1
2
3
4
5
6

这里重点是:

text
auto
  创建新对象,类型通常去掉引用和顶层 const

auto&
  推导出引用,绑定到原对象

const auto&
  以只读引用绑定,常用于避免复制又不修改对象
1
2
3
4
5
6
7
8

例如遍历容器:

cpp
std::vector<std::string> names{"Alice", "Bob"};

for (const auto& name : names) {
    // 不复制 string,也不修改元素
}
1
2
3
4
5

这里 const auto& 很常见。

它表达:

text
我只是读每个元素,不想复制,也不想修改。
1

4. 用 static_assert 验证推导结果 ​

学习类型推导,不要只靠猜。

可以用编译期检查:

cpp
#include <type_traits>

int main() {
    int x = 0;
    const int& r = x;

    auto a = r;
    auto& b = r;

    static_assert(std::is_same_v<decltype(a), int>);
    static_assert(std::is_same_v<decltype(b), const int&>);
}
1
2
3
4
5
6
7
8
9
10
11
12

static_assert 是编译期断言。

如果条件不成立,程序不能通过编译。

std::is_same_v<A, B> 用来判断两个类型是否相同。

这类小实验很适合学习现代 C++:

text
写一个最小例子
  |
  v
用 static_assert 证明类型
  |
  v
再回头理解规则
1
2
3
4
5
6
7

5. decltype:看名字,还是看表达式 ​

decltype 用来得到一个表达式或名字的类型。

但它有一个非常重要的区别:

cpp
int x = 0;

decltype(x) a = 1;
decltype((x)) b = x;
1
2
3
4

decltype(x) 是 int。

decltype((x)) 是 int&。

为什么多一层括号就变了?

因为规则不同:

text
decltype(name)
  如果 name 是未加括号的变量名,得到它的声明类型

decltype((expr))
  按表达式值类别推导
1
2
3
4
5

(x) 是一个左值表达式。

对左值表达式,decltype((x)) 得到引用类型。

这和上一篇值类别直接相连。

6. decltype(auto) 更敏感 ​

普通 auto 会丢引用。

decltype(auto) 会按 decltype 规则保留得更精确。

看函数:

cpp
int global = 42;

int& get_ref() {
    return global;
}

auto f1() {
    return get_ref();
}

decltype(auto) f2() {
    return get_ref();
}
1
2
3
4
5
6
7
8
9
10
11
12
13

f1() 返回 int。

f2() 返回 int&。

这意味着:

text
auto 返回值
  更像按值返回,可能丢引用

decltype(auto) 返回值
  更忠实保留 return 表达式类型
1
2
3
4
5

所以 decltype(auto) 很强,但也更危险。

你必须确定自己真的想保留引用。

7. 初始化语法会影响结果 ​

C++ 有多种初始化形式:

cpp
int a = 1;
int b(1);
int c{1};
1
2
3

对 int 来说差别不大。

但花括号初始化能阻止窄化转换:

cpp
int x = 3.14;  // 可能警告,结果变成 3
int y{3.14};  // 编译错误
1
2

“窄化”可以理解成:把信息更多的值塞进信息更少的类型,可能丢失信息。

比如 3.14 变成 3,小数部分没了。

花括号不是永远最好。

但它能帮助你挡住一些无意转换。

8. auto 和花括号要小心 ​

不同标准版本和写法下,auto 配合花括号可能让类型出乎意料。

cpp
auto a = 1;    // int
auto b{1};     // C++17 起通常是 int
auto c = {1};  // std::initializer_list<int>
1
2
3

尤其是:

cpp
auto values = {1, 2, 3};
1

这里 values 不是 std::vector<int>。

它是 std::initializer_list<int>。

所以写容器时要明确:

cpp
std::vector<int> values{1, 2, 3};
1

不要以为 auto 会帮你猜成你脑子里的容器类型。

9. constexpr 表示可用于编译期求值 ​

看函数:

cpp
constexpr int square(int x) {
    return x * x;
}
1
2
3

它可以用于编译期:

cpp
int values[square(3)]{};
1

这里数组大小需要编译期常量。

square(3) 可以在编译期算出 9。

但 constexpr 函数也可以在运行期调用:

cpp
#include <iostream>

int n{};
std::cin >> n;
int result = square(n);
1
2
3
4
5

因为 n 是运行期输入。

所以不要把 constexpr 理解成“永远在编译期运行”。

更准确是:

text
如果参数和上下文允许,它可以在编译期求值。
1

10. consteval 和 constinit ​

C++20 里还有两个相关词。

consteval 表示必须在编译期求值:

cpp
consteval int id(int x) {
    return x;
}
1
2
3

constinit 用在变量上,要求静态初始化:

cpp
constinit int global_value = 42;
1

三者区别:

text
constexpr
  可以用于编译期求值,也可以运行期调用

consteval
  必须编译期求值

constinit
  要求变量用静态初始化方式完成初始化
1
2
3
4
5
6
7
8

初学阶段重点掌握 constexpr。

另外两个知道用途即可。

11. 本篇总结 ​

类型推导不是偷懒工具。

它是现代 C++ 让代码减少噪声的一套规则。

核心判断:

text
auto
  通常创建新对象,会丢引用和顶层 const

auto&
  保留引用关系

const auto&
  常用于只读借用,避免复制

decltype(name)
  得到声明类型

decltype((expr))
  根据表达式值类别得到类型

decltype(auto)
  更忠实保留返回表达式类型

constexpr
  表达可以在编译期求值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

写现代 C++ 时,不要问“能不能用 auto”。

要问:

text
推导出来的类型是否就是我想表达的类型?
读代码的人能不能从初始化表达式看出它?
这里需不需要保留引用或 const?
1
2
3

最后更新于:

Pager
上一篇2. 移动、转发与值类别:std::move 为什么不移动 / Move, Forward, and Value Categories
下一篇4. 模板、实例化与 SFINAE / Templates, Instantiation, and SFINAE

持续记录,持续成长

Copyright © Tidenflow