auto 与 decltype¶
| 标准 | 难度 | 状态 | 分级 | 证据 | 审阅 | 最后校验 |
|---|---|---|---|---|---|---|
| C++11 | 2/5 | final | A | 3 条(manual 3) | 单审(zyuhao7) | 2026-10-27 |
编译器支持:GCC 4.8.1+ · Clang 2.9+ · MSVC 17.0+。版本查询:cppreference 编译器支持。
主张与证据¶
| 主张 | 支持证据 |
|---|---|
| 用 auto 承接 upper_bound 返回的迭代器类型 | ev-mold-auto-upper-bound-001 |
| 用 decltype 复用结构体成员的声明类型定义别名 | ev-mold-decltype-offset-002 |
| 用 decltype 查询 awaiter 的 await_resume 返回类型 | ev-drogon-decltype-await-result-003 |
本站证据页:ev-drogon-decltype-await-result-003 · ev-mold-auto-upper-bound-001 · ev-mold-decltype-offset-002
解决什么问题¶
泛型代码里的类型常常又长又依赖上下文。std::map<std::string, std::vector<int>>::const_iterator 这样的类型写一次还好,写在循环、lambda 和模板里就会淹没逻辑。更麻烦的是,有些类型根本无法直接命名:lambda 的闭包类型、依赖模板参数的表达式结果、decltype 才能描述的成员类型。
C++11 引入 auto 和 decltype 解决两类问题。auto 让编译器从初始化器推导变量类型,调用点只写 auto;decltype(expr) 在编译期给出表达式的精确类型,保留引用和 cv 限定,常用于尾置返回类型和类型别名。标准提案为 N1984(auto)与 N2343/N3276(decltype)。
基础用法¶
auto 必须有初始化器;decltype 接受任意表达式。C++11 的函数返回类型若需推导,要写成尾置返回类型。
#include <cstddef>
#include <iostream>
#include <vector>
template <typename Container>
auto middle(const Container& values) -> decltype(values.begin()) {
return values.begin() + static_cast<std::ptrdiff_t>(values.size() / 2);
}
int main() {
std::vector<int> values{10, 20, 30, 40, 50};
auto it = middle(values);
decltype(values.size()) count = values.size();
std::cout << *it << ' ' << count << '\n';
}
输出 30 5。auto it 的迭代器类型由 middle 的返回类型决定,decltype(values.size()) 精确取得 size() 的返回类型。完整版本见 examples/cpp11/auto_decltype.cpp。
进阶用法¶
auto 的推导规则与模板参数推导一致:顶层 const 和引用被丢弃,数组和函数退化为指针。要保留引用需写 auto& 或 const auto&;要保留 const 需写 const auto。这让 auto 适合承接迭代器、函数返回值与局部副本,但不适合无条件替代所有显式类型。
decltype 的规则不同:decltype(x) 给出变量声明时的类型,decltype((x)) 对带括号的左值表达式给出引用类型。这个差异在泛型代码里很关键,也是 C++14 decltype(auto) 存在的原因。C++14 还允许普通函数用 auto 推导返回类型,不再需要尾置写法;C++11 中 auto f() 没有尾置返回类型是非法的。
尾置返回类型让返回类型可以引用参数:template <class T, class U> auto add(T a, U b) -> decltype(a + b)。C++14 的 decltype(auto) 则把推导规则交给 decltype,适合完美转发包装器。C++20 的简写函数模板进一步允许 auto 参数,但返回类型推导规则不变。
真实项目证据¶
| 项目 | 位置 | 用法 | 为什么典型 |
|---|---|---|---|
| mold | src/mold.h#L2624-L2630 |
用 auto 承接 upper_bound 的迭代器 |
迭代器类型依赖容器与模板参数,写出来冗长 |
| mold | src/gdb-index.cc#L406-L411 |
用 decltype 复用字段类型定义偏移别名 |
别名与字段声明自动同步,避免重复类型 |
| drogon | lib/inc/drogon/utils/coroutine.h#L64-L69 |
用 decltype 查询 await_resume 类型 |
协程结果类型无法直接命名,只能由表达式推导 |
三条证据均为 manual 级:作者复核,尚未引入第二名审核者。三处都在生产路径上,覆盖迭代器推导、类型别名与依赖表达式三种用途。
陷阱与边界¶
auto 会丢弃引用和顶层 const。 从函数返回引用时,auto x = f() 得到的是副本;要绑定引用必须写 auto& 或 const auto&。
代理对象不是值。 std::vector<bool> 的 operator[] 返回代理引用,auto bit = v[0] 得到代理对象而非 bool;需要真实值时要显式写 bool 或 static_cast。
decltype 的括号陷阱。 decltype(x) 是变量类型,decltype((x)) 是左值引用类型;宏和模板里多一层括号可能改变结果。
尾置返回类型不能省。 C++11 中 auto f() { return 1; } 非法,必须写 auto f() -> int;C++14 才允许省略。
不要用 auto 隐藏转换。 需要显式窄化、接口类型或文档作用时,写清楚类型;auto 适合局部实现细节,不适合公共接口的契约表达。
函数参数不能用 auto。 C++11/14 的普通函数参数不能写 auto;C++14 仅泛型 lambda 允许,C++20 才支持简写函数模板。
库联动¶
- 标准容器与算法:迭代器类型几乎总是用
auto承接,begin()、find()、upper_bound()的返回值无需拼写。 <type_traits>:decltype与std::declval组合,是 SFINAE 和返回类型推导的基础工具。- 智能指针与 RAII:
auto ptr = std::make_unique<T>()避免重复写std::unique_ptr<T>。 - mold / drogon:链接器用
auto简化迭代器,协程工具用decltype查询无法命名的结果类型。
横向对比¶
| 方案 | 保留引用 | 保留 const |
可用于返回类型 | 主要限制 |
|---|---|---|---|---|
| 显式类型 | 是 | 是 | 是 | 冗长,依赖上下文时难以书写 |
auto |
否 | 否 | C++14 起可 | 丢弃引用与顶层 const |
decltype(expr) |
是 | 是 | 是 | 括号会改变左值/引用结果 |
decltype(auto) |
是 | 是 | C++14 起可 | 推导规则隐式,阅读成本较高 |
跨标准演进:C++11 通过 N1984 引入 auto、通过 N2343/N3276 完善 decltype;C++14 增加返回类型推导、decltype(auto) 与泛型 lambda;C++20 允许简写函数模板参数。