C++类型推断和类型推导规则
C++类型推断和类型推导规则
类型推断
auto
auto
只能初始化用,用于自动推断类型,编译期开始.
注意,auto
只是推断值类型,const,引用这种修饰是直接去掉的.当然也可以搭配这些关键字
从 C++ 14 起,auto
能用于 lambda 表达式中的函数传参,而 C++ 20 起该功能推广到了一般的函数和模板返回值。考虑下面的例子:
auto add14 = [](auto x, auto y) -> int {
return x+y;
}
auto add20(auto x, auto y) {
return x+y;
//编译器自动生成一堆模板.
auto i = 5; // type int
auto j = 6; // type int
std::cout << add14(i, j) << std::endl;
std::cout << add20(i, j) << std::endl;
[!WARNING]
auto
还不能用于推导数组类型:auto auto_arr2[10] = {arr}; // 错误, 无法推导数组元素类型 2.6.auto.cpp:30:19: error: 'auto_arr2' declared as array of 'auto' auto auto_arr2[10] = {arr};
也不允许在生成模板时直接使用,比如
vector<auto>
decltype
decltype
关键字是为了解决 auto
关键字只能对变量进行类型推导的缺陷而出现的。用于处理表达式.
所以decltype可以推断const,引用等值类别,会严格保留相应表达式类别,而不只是类型.
比较常见用法就是:
int a = 10;
double b = 100.1;
decltype (a+b) c = a + b;
//decltype可以推导表达式结果的类型,虽然这里用auto也可以啦.
C++11 还引入了一个叫做尾返回类型(trailing return type),利用 auto
关键字将返回类型后置来推断模板返回值类型:
template<typename T, typename U>
auto add2(T x, U y) -> decltype(x+y){
return x + y;
}
令人欣慰的是从 C++14 开始是可以直接让普通函数具备返回值推导,因此下面的写法变得合法:
template<typename T, typename U>
auto add3(T x, U y){
return x + y;
}
decltype(auto)
decltype(auto)
会像 decltype
一样分析表达式的类型和值类别,但语法上像 auto
一样不需要显式指定表达式。
类型推导规则
实际上类型推导规则是比较符合人的第一感觉的,这也正是它追求的目标,所以不用特意记,了解一下即可.
模板类型推断
auto的类型推断实际上基于模板,所以我们来看看模板的规则
假设一个基本情况:
template<Typename T>
f(ParamT param);
f(expr);
ParamT
和T
本身的推断是不同的,原因很简单,ParamT
可以带上各种修饰符号,比如指针,比如const
.
这里有三种情况:
ParamType
是一个指针或引用,但不是通用引用ParamType
是一个通用引用ParamType
既不是指针也不是引用
第一种情况
expr
如果是引用,忽略引用部分,然后与ParamT
进行模式匹配决定T
举个例子,如果这是我们的模板,
template<typename T>
void f(T& param); //param是一个引用
我们声明这些变量,
int x=27; //x是int
const int cx=x; //cx是const int
const int& rx=x; //rx是指向作为const int的x的引用
在不同的调用中,对param
和T
推导的类型会是这样:
f(x); //T是int,param的类型是int&
f(cx); //T是const int,param的类型是const int&
f(rx); //T是const int,param的类型是const int&
第二种模式
- 如果
expr
是左值,T
和ParamType
都会被推导为左值引用。这非常不寻常,第一,这是模板类型推导中唯一一种T
被推导为引用的情况。第二,虽然ParamType
被声明为右值引用类型,但是最后推导的结果是左值引用。 - 如果
expr
是右值,就使用正常的(也就是情景一)推导规则
举个例子:
template<typename T>
void f(T&& param); //param现在是一个通用引用类型,带有推导的&&的都是通用引用.
int x=27; //如之前一样
const int cx=x; //如之前一样
const int & rx=cx; //如之前一样
f(x); //x是左值,所以T是int&,
//param类型也是int&
f(cx); //cx是左值,所以T是const int&,
//param类型也是const int&
f(rx); //rx是左值,所以T是const int&,
//param类型也是const int&
f(27); //27是右值,所以T是int,
//param类型就是int&&
通用引用的情况就是如果左值传递就是左值引用,如果右值传递就是右值引用.
其实这里的T
带有&,可以直接带入在paramT
中,去掉重复(引用折叠/坍缩规则)就可以发现就是paramT
第三种情况
这意味着无论传递什么param
都会成为它的一份拷贝——一个完整的新对象。事实上param
成为一个新对象这一行为会影响T
如何从expr
中推导出结果。
- 和之前一样,如果
expr
的类型是一个引用,忽略这个引用部分 - 如果忽略
expr
的引用性(reference-ness)之后,expr
是一个const
,那就再忽略const
。如果它是volatile
,也忽略volatile
template<typename T>
void f(T param); //以传值的方式处理param
int x=27; //如之前一样
const int cx=x; //如之前一样
const int & rx=cx; //如之前一样
f(x); //T和param的类型都是int
f(cx); //T和param的类型都是int
f(rx); //T和param的类型都是int
因为这是一个拷贝.
特殊情况
如果数组是采取第一种情况传递进去的,会保留数组类型,不会退化,保留程度,于是我们可以写出这个:
//在编译期间返回一个数组大小的常量值(//数组形参没有名字,
//因为我们只关心数组的大小)
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
return N;
}
虽然没啥用就是了.
auto类型推断
与模板差不多,auto
类型推导和模板类型推导有一个直接的映射关系。它们之间可以通过一个非常规范非常系统化的转换流程来转换彼此。
template<typename T>
void f(ParmaType param);
f(expr); //使用一些表达式调用f
//当一个变量使用auto进行声明时,auto扮演了模板中T的角色,变量的类型说明符扮演了ParamType的角色。
只有一个例外,那就是初始化列表.
如何查看类型推断
用你的lsp,我的伙计.