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限定符会同步到推断结果。
评论区