现代的编程语言,无论是动态语言(JavaScript、Python 等),仍是静态语言(Go、Rust 等),大都支持自动类型推导(type deduction)。c++
自动类型推导,通俗地讲就是定义一个变量的时候不须要明确指定类型,而是让编译器根据上下文进行推导。编程
在 C++11 以前,模板(template)代码就支持编译器自动类型推导。C++11 很重要的一个特性就是增强了编译器自动类型推导的能力,使之不限于模板 —— 与此相关的关键字有两个 auto
和 decltype
。编程语言
咱们来看看 auto 关键字在 C++ 中的使用。 最简单的用法,定义变量的时候不指定类型,经过初始化的值让编译器自动推导。函数
auto a; // 编译不经过
auto b = 0; // b 是 int 类型
auto c = 0ull; // c 是 unsigned long long 类型
auto d = "Hello World"; // d 是 const char* 类型
auto e = std::string("Hello"); // e 是 std::string 类型
复制代码
auto 和容器类型、迭代器一块儿配合使用,能够少打不少字,代码也更简洁、清晰。工具
std::vector<int> v(10, 1);
auto itr_begin = v.begin(); // std::vector<int>::iterator
auto itr_end = v.end(); // std::vector<int>::iterator
auto sz = v.size(); // std::vector<int>::size_type
复制代码
若是不用自动类型推导,下面 v 的类型写起来也很麻烦。若是 b 和 e 是自定义的迭代器,不必定能用 typename std::iterator_traits<Iter>::value_type
来得到类型。性能
template<typename Iter>
void Process(Iter b, Iter e) {
while (b != e) {
auto v = *b; // 若是不用自动类型推导,如何得到 *b 的类型
// typename std::iterator_traits<Iter>::value_type v = *b;
std::cout << v << std::endl;
++b;
}
}
复制代码
类型推导能够和 Lambda 表达式一块儿愉快地使用。ui
auto Plus = [](int a, int b) { return a + b; };
复制代码
也许有人会说,Lambda 表达式能够用一个 std::function 对象来包装。spa
std::function<int(int, int)> PlusFunc = [](int a, int b) { return a + b; };
复制代码
可是这样作有几点很差:code
auto Plus = [](auto a, auto b) { return a + b; }; // std::function<T> 的类型无法写了
std::cout << Plus(3, 4) << std::endl;
std::cout << Plus(3.14, 1.11) << std::endl;
std::cout << Plus(std::string("hello"), std::string("world")) << std::endl;
复制代码
某些状况下,自动类型推导还可让你避免一些“坑”。好比:对象
std::unordered_map<std::string, int> m;
// ...
for (const std::pair<std::string, int>& pa : m) { // 你以为有没有问题?
// ...
}
复制代码
看得出上面这段代码有什么问题吗?
上面的代码会致使复制整个 unordered_map。由于 std::unordered_map<Key, T>::value_type 的类型是 std::pair<const Key, T>。正确的写法应该是:
for (const std::pair<const std::string, Foo>& pa : m) {
// ...
}
复制代码
用自动类型推导能够简单避免这个坑:
for (const auto& pa : m) {
// ...
}
复制代码
固然,用自动类型推导的时候,也可能引入一些坑。好比:
std::vector<bool> v2;
v2.push_back(true);
v2.push_back(false);
auto b2 = v2[0]; // b2 是什么类型?
复制代码
由于 std::vector 的特殊实现缘由,变量 b2 不是一个 bool 类型,而是一个自定义的类。(不管你是否使用自动类型推导,都尽量不要使用 std::vector。)
decltype 的做用是,告诉你一个表达式/变量/常量是什么类型。好比:
std::cout << typeid(decltype(1)).name() << std::endl; // 输出 i,表示 int
float f;
std::cout << typeid(decltype(f)).name() << std::endl; // 输出 f,表示 float
unsigned a = 1;
unsigned long long b = 2;
std::cout << typeid(decltype(a + b)).name() << std::endl; // 输出 y,表示 unsigned long long
复制代码
typeid(T).name() 在不一样的编译器下的输出可能不同。本文在 Ubuntu 上使用 gcc 7.5 进行编译。typeid(T).name() 的输出能够经过 c++filt 工具转换成实际可读的类型名称。
相比 auto,decltype 用得少不少。 举一个例子:
template<typename T, typename U>
??? Plus(T t, U u)
return t + u;
}
复制代码
t + u 到底应该返回什么类型?
Plus(1, 2); // 返回值类型应该是 int
Plus(1, 2.0); // 返回值类型应该是 double
复制代码
使用 decltype 的 trailing return type 来解决这个问题:
template<typename T, typename U>
auto Plus(T t, U u) -> decltype(t + u) {
return t + u;
}
复制代码
C++ 14 进行了增强,能够省掉这条尾巴。
template<typename T, typename U>
auto Plus(T t, U u) {
return t + u;
}
复制代码
若是函数有多个 return 语句,须要保证它们返回的类型都是同样的才能成功编译。
// error: inconsistent deduction for auto return type: ‘int’ and then ‘double’
auto f(int i) {
if (i == 1) {
return 1;
} else {
return 2.0;
}
}
复制代码
使用 auto 须要本身手动说明是值类型仍是引用类型。C++14 引入 decltype(auto) 来自动推导精确类型——其实 decltype(auto) 算是 decltype(expr) 的一个语法糖。
std::vector<std::string> v{"C++98", "C++03", "C++11",
"C++14", "C++17", "C++20"};
// v[0] 的返回值类型是 std::string&,可是 a 是 std::string
auto a = v[0];
// a 是 std::string&
auto& b = v[0];
// C++11,咱们能够这样肯定精确类型,c 是 std::string&
// 可是,若是 v[0] 变成一个复杂的表达式,代码写出来可能很难看懂
decltype(v[0]) c = v[0];
// C++14 引入了 decltype(auto),能够自动推导出精确类型。d 是 std::string&
decltype(auto) d = v[0];
复制代码