文章

条款2:auto类型推导

根据初始化表达式推导变量类型,自动剥除引用和顶层const,生成具体值类型或引用(需显式声明)。

条款2:auto类型推导

条款2:auto 类型推导

auto 与模板类型推导的对应关系

虽然 auto 不直接用于模板、函数或形参,但它与模板类型推导之间存在一一映射的关系,可以通过一个非常规范和系统的流程相互转换。

对应关系类比

1
2
3
4
template<typename T>
void f(ParamType param);     // 模板形式

f(expr);                     // 使用 expr 推导 T 和 ParamType

等价于:

1
auto x = expr;               // auto 扮演 T,auto 所在位置的类型说明符扮演 ParamType

示例转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
auto x = 27;
// 相当于:
template<typename T>
void func_for_x(T param);
func_for_x(27);              // T 推导为 int

const auto cx = x;
// 相当于:
template<typename T>
void func_for_cx(const T param);
func_for_cx(x);              // T 推导为 int,cx 类型为 const int

const auto& rx = x;
// 相当于:
template<typename T>
void func_for_rx(const T& param);
func_for_rx(x);              // T 推导为 int,rx 类型为 const int&

auto 推导对应模板类型推导的三种情景

在模板类型推导中,将推导情况分为三种:

情景类型说明符
1指针或引用,但不是万能引用
2万能引用(T&&
3既不是指针也不是引用(非引用类型)

对应 auto 示例:

1
2
3
4
5
6
7
auto x = 27;                  // 情景三:非引用类型
const auto cx = x;            // 情景三:非引用类型
const auto& rx = cx;          // 情景一:非万能引用

auto&& uref1 = x;             // 情景二:x 是 int 左值,推导为 int&
auto&& uref2 = cx;            // 推导为 const int&
auto&& uref3 = 27;            // 右值,推导为 int&&

数组和函数的退化行为

auto 类型推导中,数组和函数名的退化与模板推导一致:

1
2
3
4
5
6
7
const char name[] = "flash bang";
auto arr1 = name;             // const char*
auto& arr2 = name;            // const char (&)[11]

void someFunc(int, double);
auto func1 = someFunc;        // void (*)(int, double)
auto& func2 = someFunc;       // void (&)(int, double)

唯一的区别:大括号初始化 {}

这是 auto 类型推导与模板类型推导唯一的重大区别

1
2
3
4
5
6
7
8
9
10
int x1 = 27;
int x2(27);
int x3 = { 27 };
int x4{ 27 };
// 四者类型均为 int,值为 27

auto x1 = 27;                 // int
auto x2(27);                  // int
auto x3 = { 27 };             // std::initializer_list<int>
auto x4{ 27 };                // std::initializer_list<int>
  • 当使用大括号 {} 初始化时,auto 会推导为 std::initializer_list<T>

  • 如果不能推导出 initializer_list(例如元素类型不一致),编译会报错:

1
auto x5 = { 1, 2, 3.0 };      // ❌ 错误:无法推导为统一类型的 initializer_list

此时发生了两层推导:

  • auto 推导变量类型为 std::initializer_list<T>
  • std::initializer_list<T> 的模板参数 T 也需要推导

模板类型推导与大括号初始化不兼容

1
2
3
4
5
6
auto x = { 11, 23, 9 };       // 推导成功:std::initializer_list<int>

template<typename T>
void f(T param);

f({ 11, 23, 9 });             // ❌ 错误:模板推导无法识别大括号中的类型

正确的模板声明方式:

1
2
3
4
template<typename T>
void f(std::initializer_list<T> initList);

f({ 11, 23, 9 });             // ✔️ T 推导为 int,initList 类型为 std::initializer_list<int>

C++14 中 auto 的新用法限制

C++14 扩展了 auto 的用法 —— 可以用于函数返回值、lambda 参数中,但这时并不采用 auto 类型推导,而是使用模板类型推导规则

函数返回值中的 auto

1
2
3
auto createInitList() {
    return { 1, 2, 3 };       // ❌ 错误:无法推导出 return 列表的类型
}

lambda 参数中的 auto

1
2
3
4
5
std::vector<int> v;

auto resetV = [&v](const auto& newValue){ v = newValue; };

resetV({ 1, 2, 3 });          // ❌ 错误:无法推导 { 1, 2, 3 } 的类型

总结

  • auto 类型推导几乎等同于模板类型推导,两者是等价体系。
  • 唯一的差异是:
    • auto 遇到大括号初始化({})时会尝试推导为 std::initializer_list<T>
    • 模板类型推导则对 {} 无能为力,除非显式指定类型为 std::initializer_list<T>
  • C++14 扩展的用法(函数返回值、lambda 参数中的 auto)实际使用的是模板类型推导规则,不支持 {} 初始化。

  • 在 C++11/14 编程中要警惕 auto 的大括号初始化陷阱,除非你明确需要 std::initializer_list,否则建议优先使用 =() 方式初始化。
本文由作者按照 CC BY 4.0 进行授权