1 auto 关键字
auto 关键字的官方解释是 placeholder type specifiers,直译过来叫占位类型说明符,顾名思义,它是一个占位符号,会在编译阶段被替换掉,下面是 auto 从 C++ 11 - 20 的功能定义。
功能定义与代码示例
// 功能1:对于变量,要从它的初始化器自动推导出它的类型,C++ 11 定义支持
auto i = 50;
// 功能2: 对于函数,要从它的 return 语句推导出它的返回类型,C++ 14 定义支持
auto bar(int a, double b) {
return a + b;
}
// 功能3:对于lamda表达式形参,要从实参推导出它的类型,C++ 14 定义支持
auto f = [] (auto a, auto b) {
return a - b;
};
std::cout << f(4, 3) << std::endl;
// 功能4:对于非类型模板形参,要从实参推导出它的类型,C++ 17 定义支持
template<auto c>
auto bar(int a, double b) {
return a + b + c;
}
bar<4>(3, 0.2);
// 功能5:结构化绑定声明,C++ 17 定义支持
// 使用结构化绑定和 auto 来解包元组
std::tuple<int, double, std::string> t = {1, 2.3, "example"};
auto [a, b, c] = t;
// 功能6:简写函数模板,C++ 20 定义支持 (部分较新的编译器在C++14就支持了此功能,我测试 g++-9.4.0 是支持的)
auto bar(auto a, auto b) {
return a + b;
}推导规则与代码示例
规则1
如果auto声明的变量是按值初始化,则推导出的类型会忽略cv限定符。进一步解释为,在使用auto声明变量时,既没有使用引用,也没有使用指针,那么编译器在推导的时候会忽略const和volatile限定符。当然auto本身也支持添加cv限定符:
const int i = 5;
auto j = i; // auto推导类型为int,而非const int
auto &m = i; // auto推导类型为const int,m推导类型为const int&
auto *k = i; // auto推导类型为const int,k推导类型为const int*
const auto n = j; // auto推导类型为int,n的类型为const int根据规则1,在上面的代码中,虽然i是const int类型,但是因为按值初始化会忽略cv限定符,所以j的推导类型是int而不是const int。而m和k分别按引用和指针初始化,因此其cv属性保留了下来。另外,可以用const结合auto,让n的类型推导为const int。
规则2
使用auto声明变量初始化时,目标对象如果是引用,则引用属性会被忽略:
int i = 5;
int &j = i;
auto m = j; // auto推导类型为int,而非int&根据规则2,虽然j是i的引用,类型为int&,但是在推导m的时候会忽略其引用。
规则3
使用auto和万能引用(见一文搞懂 C++ 11 右值引用)声明变量时,对于左值会将auto推导为引用类型:
int i = 5;
auto&& m = i; // auto推导类型为int& (这里涉及引用折叠的概念)
auto&& j = 5; // auto推导类型为int根据规则3,因为i是一个左值,所以m的类型被推导为int&,auto被推导为int&,这其中用到了引用折叠的规则。而5是一个右值,因此j的类型被推导为int&&,auto被推导为int。
规则4
使用auto声明变量,如果目标对象是一个数组或者函数,则auto会被推导为对应的指针类型:
int i[5];
auto m = i; // auto推导类型为int*
int sum(int a1, int a2)
{
return a1+a2;
}
auto j = sum // auto推导类型为int (__cdecl *)(int,int)根据规则4,虽然i是数组类型,但是m会被推导退化为指针类型,同样,j也退化为函数指针类型。
规则5
当auto关键字与列表初始化组合时,这里的规则有新老两个版本,这里只介绍新规则(C++17标准):
(1)直接使用列表初始化,列表中必须为单元素,否则无法编译,auto类型被推导为单元素的类型。
(2)用等号加列表初始化,列表中可以包含单个或者多个元素,auto类型被推导为std::initializer_list<T>,其中T是元素类型。请注意,在列表中包含多个元素的时候,元素的类型必须相同,否则编译器会报错。
auto x1 = { 1, 2 }; // x1类型为 std::initializer_list<int>
auto x2 = { 1, 2.0 }; // 编译失败,花括号中元素类型不同
auto x3{ 1, 2 }; // 编译失败,不是单个元素
auto x4 = { 3 }; // x4类型为std::initializer_list<int>
auto x5{ 3 }; // x5类型为int在上面的代码中,x1根据规则5(2)被推导为std::initializer_list<T>,其中的元素都是int类型,所以x1被推导为std::initializer_list<int>。同样,x2也应该被推导为std::initializer_list<T>,但是显然两个元素类型不同,导致编译器无法确定T的类型,所以编译失败。根据规则5(1),x3包含多个元素,直接导致编译失败。x4和x1一样被推导为std::initializer_list<int>,x5被推导为单元素的类型int。
2 decltype 关键字
decltype 是“declare type”的缩写,译为“声明类型”,它的作用是检查实体的声明类型或表达式的类型和值类别。
说到检查类型,很多同学肯定会想到typeid ,但是可惜的是它是在运行期(RTTI)确定对象类型信息,但是很多场景下,我们希望编译期就将对象类型信息确定,所以C++ 11引入了decltype 。
decltype(e)(其中e的类型为T)的推导规则有如下5条:
如果
e是一个未加括号的标识符表达式(结构化绑定除外)或者未加括号的类成员访问,则decltype(e)推断出的类型是e的类型T。如果并不存在这样的类型,或者e是一组重载函数,则无法进行推导。如果
e是一个函数调用或者仿函数调用,那么decltype(e)推断出的类型是其返回值的类型。如果
e是一个类型为T的左值,则decltype(e)是T&。如果
e是一个类型为T的将亡值,则decltype(e)是T&&。除去以上情况,则
decltype(e)是T。
根据这5条规则,我们来看一看C++标准文档给的几个例子:
const int&& foo();
int i;
struct A {
double x;
};
const A* a = new A();
decltype(foo()); // decltype(foo())推导类型为const int&&
decltype(i); // decltype(i)推导类型为int
decltype(a->x); // decltype(a->x)推导类型为double
decltype((a->x)); // decltype((a->x))推导类型为const double&在上面的代码中,decltype(foo())满足规则2和规则4,foo函数的返回类型是const int&&,所以推导结果也为const int&&;decltype(i)和decltype(a->x)很简单,满足规则1,所以其类型为int和double;最后一句代码,由于decltype((a->x))推导的是一个带括号的表达式(a->x),因此规则1不再适用,但很明显a->x是一个左值,又因为a带有const限定符,所以其类型被推导为const double&。
cv限定符的推导规则:
通常情况下,decltype(e)所推导的类型会同步e的cv限定符,比如:
const int i = 0;
decltype(i); // decltype(i)推导类型为const int但是还有其他情况,当e是未加括号的成员变量时,父对象表达式的cv限定符会被忽略,不能同步到推导结果:
struct A {
double x;
};
const A* a = new A();
decltype(a->x); // decltype(a->x)推导类型为double, const属性被忽略在上面的代码中,a被声明为const类型,如果想在代码中改变a中x的值,则肯定会编译失败。但是decltype(a->x)却得到了一个没有const属性的double类型。当然,如果我们给a->x加上括号,则情况会有所不同:
struct A {
double x;
};
const A* a = new A();
decltype((a->x)); // decltype((a->x))推导类型为const double&总的来说,当e是加括号的数据成员时,父对象表达式的cv限定符会同步到推断结果。
评论区