类型自动推导 - auto和decltype
auto 和 decltype 是C++11引入的强有力的类型自动推导工具. 不仅让代码变得更加简洁, 还增强了模板和泛型的表达能力
| Book | Video | Code | X |
|---|---|---|---|
| cppreference-auto / cppreference-decltype / markdown | 视频解读 / 练习讲解 | 练习代码 |
为什么引入?
- 解决类型声明过于复杂的问题
- 模板应用中, 获取对象或表达式类型的需求
- 为lambda表达式的定义做支撑
auto和decltype有什么区别?
- auto常常用于变量定义, 推导的类型可能丢失const或引用(可显式指定进行保留auto &)
- decltype获取表达式的精确类型
- auto通常无法作为模板类型参数使用
一、基础用法和场景
声明定义
充当类型占位符, 辅助变量的定义或声明。使用auto时变量必须要初始化, decltype可以不用初始化
int b = 2;
auto b1 = b;
decltype(b) b2 = b;
decltype(b) b3; // 可以不用初始化
表达式类型推导
常常用于复杂表达式的类型推导, 确保计算精度
int a = 1;
auto b1 = a + 2;
decltype(a + 2 + 1.1) b2 = a + 2 + 1.1;
auto c1 = a + '0';
decltype(2 + 'a') c2 = 2 + 'a';
复杂类型推导
迭代器类型推导
std::vector<int> v = {1, 2, 3};
// std::vector<int>::iterator it = v.begin();
auto it = v.begin(); // 自动推导it类型
// decltype(v.begin()) it = v.begin();
for (; it != v.end(); ++it) {
if (*it == 2) {
v.insert(it, 0);
break;
}
}
函数类型推导
对于函数或lambda表达式这种复杂类型, 常常使用auto和decltype. 一般, lambda定义用auto, 模板类型参数用decltype
int add_func(int a, int b) {
return a + b;
}
int main() {
auto minus_func = [](int a, int b) { return a - b; };
std::vector<std::function<decltype(add_func)>> funcVec = {
add_func,
minus_func
};
funcVec[0](1, 2);
funcVec[1](1, 2);
//...
}
函数返回值类型推导
语法糖用法
auto为后置返回类型函数定义写法做支持, 并可以配合decltype进行返回类型推导使用
auto main() -> int {
return 0;
}
auto add(int a, double b) -> decltype(a + b) {
return a + b;
}
函数模板返回值类型推导
当无法确定模板返回值时可以用auto + decltype做推导, 可以让add支持一般类型int, double,... 和 复杂类型 Point, Vec,... 增强泛型的表达能力. (c++14中可以省略decltype)
template<typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {
return a + b;
}
类/结构体成员类型推导
struct Object {
const int a;
double b;
Object() : a(1), b(2.0) { }
};
int main() {
const Object obj;
auto a = obj.a;
std::vector<decltype(obj.b)> vec;
}
二、真实案例 - STL 中的 auto/decltype
前述示例用于讲解语法, 而 auto/decltype 的实际价值, 在标准库自身的实现中体现得最为直接。下面从仓库内置的 MSVC STL 中选取两段常见代码, 分别演示
auto与decltype;_STD等是库内部的宏与限定写法, 阅读时关注auto/decltype即可
auto 推导迭代器与元素类型:遍历容器
遍历容器是 auto 最常见的场景。下面是 <filesystem> 规范化路径时的一段遍历:auto _Pos 推导出迭代器类型, const auto _Elem 推导出解引用后的元素类型 —— 与 ## 一「复杂类型推导 - 迭代器」里的 auto it = v.begin() 完全是同一种写法
// MSVC STL · msvc-stl/stl/inc/filesystem (有删节, 缩进保留源码层级)
auto _New_end = _Vec.begin();
for (auto _Pos = _Vec.begin(); _Pos != _Vec.end();) {
const auto _Elem = *_Pos++;
// ...(根据 _Elem 决定是否写回 _New_end, 略)
}
_Vec 是路径分量的容器, 它的迭代器类型与元素类型都交给 auto 推导, 无需写出具体类型
decltype 取变量的类型:std::lower_bound 的二分查找
decltype 最直接的用法就是"取某个变量或表达式的类型"。标准库的二分查找 std::lower_bound 里, 先用 auto 推导出区间长度 _Count, 再用 decltype(_Count) 表示"与 _Count 相同的类型", 把 _Count / 2 转换回该类型:
// MSVC STL · msvc-stl/stl/inc/xutility (有删节) —— std::lower_bound
auto _UFirst = _STD _Get_unwrapped(_First);
auto _Count = _STD distance(_UFirst, _STD _Get_unwrapped(_Last));
while (0 < _Count) { // divide and conquer, find half that contains answer
const auto _Count2 = static_cast<decltype(_Count)>(_Count / 2);
const auto _UMid = _STD next(_UFirst, _Count2);
// ...(在 _UMid 处比较, 缩小区间, 略)
}
这正是 ## 一「声明定义」中 decltype(b) b2 的同款用法: decltype(_Count) 就是"_Count 的类型"。auto 负责把类型推导出来, decltype 负责在别处复用同一个类型
小结: 遍历容器、复用某个变量的类型 —— 这些日常写法在标准库内部用的正是本章这套 auto + decltype 工具。这也是 C++11 引入二者的核心动机之一
三、注意事项
auto 的 const / 引用剥离
auto 推导会剥离顶层 const 和引用, 想保留得显式写
const auto&/auto&; decltype 则精确保留声明类型
int a = 1;
int &b = a;
const int c = 1;
const int &d = c;
auto a1 = a; // int
auto b1 = b; // int
auto c1 = c; // int
auto d1 = d; // int
const auto c2 = c; // const int
const auto &d2 = d; // const int &
decltype(c) c3 = c; // const int
decltype(d) d3 = d; // const int &
这也解释了前面「类/结构体成员类型推导」里 auto a = obj.a; 为什么得到的是 int 而非 const int —— 顶层 const 被 auto 剥离了
decltype(obj) 和 decltype( (obj) )的区别
- 一般
decltype(obj)获取的是其声明类型 - 而
decltype( (obj) )获取的是(obj)这个表达式的类型(左值表达式)
int a = 1;
decltype(a) b; // 推导结果为a的声明类型int
decltype( (a) ) c; // 推导结果为(a)这个左值表达式的类型 int &
decltype(obj.b) 和 decltype( (obj.b) )的区别
decltype( (obj.b) ): 从表达式视角做类型推导, obj定义类型会影响推导结果. 例如, 如果obj被const修饰时, const会限定obj.b的访问为constdecltype(obj.b): 由于推导的是成员声明类型, 所以不会受obj定义的影响
struct Object {
const int a;
double b;
Object() : a(1), b(2.0) { }
};
int main() {
Object obj;
const Object obj1;
decltype(obj.b) // double
decltype(obj1.b) // double
decltype( (obj.b) ) // double &
decltype( (obj1.b) ) // 受obj1定义的const修饰影响, 所以是 const double &
}
右值引用变量, 在表达式中是左值
int &&b = 1;
decltype(b) // 推导结果是声明类型 int &&
decltype( (b) ) // 推导结果是 int &
四、练习代码
练习代码主题
- 0 - 声明定义
- 1 - 表达式类型推导
- 2 - 复杂类型推导 - 迭代器 / 函数
- 3 - 函数返回值类型推导
- 4 - 类/结构体成员类型推导
- 5 - const 与引用的剥离和保留
练习代码自动检测命令
还没有 d2x?点击展开获取方式
# 1. 安装 xlings(Linux / macOS)
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash
# Windows PowerShell:
# irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex
# 2. 安装 d2x 并拉取本教程
xlings install d2x -y
d2x install d2mcpp
# 3. 进入项目目录 & 运行检查命令
cd d2mcpp
d2x checker auto-and-decltype