类型推导
Published on
C++是强类型语言,使用每个变量前必须声明其类型。如果不进行类型推导,则用户必须自行声明所有变量的类型,对于一些复杂的类型(如容器的迭代器)或临时类型(如lambda表达式),这是没有意义甚至无法完成的。为此,C++11标准引入了auto与decltype两个关键字用于进行类型推导,使得编译器可以自动推导出变量的类型。这样做有以下好处:
- 鲁棒性:即使更改表达式的类型也能正常工作
- 性能:可以保证没有进行转换
- 可用性:不必担心类型名称拼写困难或拼写有误
- 效率:代码会变得更高效
auto
在C++11之前,auto关键字为存储期说明符,但是基本不使用。C++11之后将此关键字用于类型推导,其基本语法是:
auto 变量名 初始化器;
基本用法
// 通用初始化语法
auto a{42};
// 赋值语法
auto b = 0;
// 通用赋值语法,它结合了上述两种形式
auto c = {3.14159};
// 直接初始化或构造函数样式的语法
auto d(1.41421f);
// 通过表达式推导
auto e = 1 + 2;
// 通过函数返回值推导
auto f = add(1, 1.2);
// 推导new操作符结果
auto g = new auto(10);
// 推导lambda表达式类型
auto lambda1 = [](int x) { return x + 3; };
// 可在一行定义多个变量,但是不能出现冲突
auto x = a, &y = b, *z = &e;
// C++14后可自动推导函数返回值
auto func() { return 2 + 3; }
// C++14后可自动推导lambda函数的参数
auto lambda2 = [](auto a) { return a; };
// C++17后可自动推导非类型模板参数
template <auto I> struct A;
// 在for循环中使用
auto vec = std::vector<int>();
for (auto it = vec.cbegin(); it != vec.cend(); ++it) {}
for (auto v : vec) {}
使用大括号初始化时的类型推导
auto A = {1, 2}; // std::initializer_list<int>
auto B = {3}; // std::initializer_list<int>
auto C{4}; // C++17后为int,之前为std::initializer_list<int>
auto D = {5, 6.7}; // 错误,初始化列表中元素类型不同
auto E{8, 9}; // C++17后非法,之前为std::initializer_list<int>
引用与cv限定符
使用auto进行类型推导时会删除引用、const限定符和volatile限定符,如有需要可自行添加。对于指针无影响。
// 一个int类型与一个int&类型变量
int a = 10;
int &b = a;
// 使用auto会删除引用
auto c = a; // c为int
auto d = b; // d同样为int
// 手动添加引用、const或volatile
auto &e = a; // e为int&
const auto f = b; // f为const int
// 对于指针,auto与auto*均可
auto g = &a; // g为int*
auto *h = &a; // h为int*
错误使用
// 必须初始化
auto a;
// 一行内出现冲突
auto b = 1, c = 1.2;
int arr[10] = {0};
// auto_arr推导为指针
auto auto_arr = arr;
// 不能用于推导数组
auto auto_arr2[10] = arr;
decltype
decltype关键字是为了解决auto关键字只能对变量进行类型推导的缺陷而出现的,其基本语法如下:
decltype(实体/表达式)
编译器推导参数的类型并返回,不会实际计算表达式的值。
推导规则
- 如果参数是没有括号的标识表达式或没有括号的类成员访问表达式,返回该实体的类型
- 如果参数是其它类型为T的表达式
- 如果表达式是将亡值,返回T&&
- 如果表达式是左值,返回T&
- 如果表达式是纯右值,返回T
- 如果对象带有括号,那么它会被当做左值表达式,从而decltype(x)和decltype((x))通常是不同的类型。
- decltype与auto不同,decltype不会删除引用、const限定符与volatile限定符。
struct A { double x; };
const A *a;
decltype(a->x) y; // y的类型是double(其声明类型)
decltype((a->x)) z = y; // z的类型是const double&(左值表达式)
int i = 33;
decltype(i) j = i * 2; // j的类型为int
// 一个lambda表达式,其类型是独有且无名的
auto f = [](int a, int b) -> int
{
return a * b;
};
decltype(f) g = f; // g的类型与f相同
自动推导函数的返回值类型
如果一个函数的返回值类型可由其参数类型确定,如下面的函数:
template <typename T, typename U>
decltype(x + y) add(T x, U y)
{
return x + y;
}
但是上述写法是错误的,在C++11中可以使用拖尾返回类型解决:
template <typename T, typename U>
auto add(T x, U y) -> decltype(x + y)
{
return x + y;
}
从C++14开始,可以直接使用auto进行推导:
template <typename T, typename U>
auto add(T x, U y)
{
return x + y;
}
decltype(auto)
使用auto进行类型推导会删除引用与cv限定符,如果想要保留,可以使用decltype(auto)。
int a = 0;
// 使用decltype(auto) x = expr;推导变量类型,相当于decltype(expr) x = expr;
decltype(auto) c1 = a; // c1的类型是int
decltype(auto) c2 = (a); // c2的类型是int&
decltype(auto) z = {1, 2}; // 错误,{1, 2}不是表达式
// 使用decltype(auto)推导函数返回值类型,可保留返回值的引用与cv限定符
template <class F, class... Args>
decltype(auto) PerfectForward(F fun, Args &&...args)
{
return fun(std::forward<Args>(args)...);
}