侧边栏壁纸
博主头像
Billy 的技术空间博主等级

君子生非异也,善假于物也。

  • 累计撰写 17 篇文章
  • 累计创建 1 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录
C++

C++11后的”新"关键字 auto 与 decltype

billy
2021-12-31 / 0 评论 / 0 点赞 / 114 阅读 / 8311 字

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声明变量时,既没有使用引用,也没有使用指针,那么编译器在推导的时候会忽略constvolatile限定符。当然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,在上面的代码中,虽然iconst int类型,但是因为按值初始化会忽略cv限定符,所以j的推导类型是int而不是const int。而mk分别按引用和指针初始化,因此其cv属性保留了下来。另外,可以用const结合auto,让n的类型推导为const int

规则2

使用auto声明变量初始化时,目标对象如果是引用,则引用属性会被忽略:

int i = 5;
int &j = i;
auto m = j;    // auto推导类型为int,而非int&

根据规则2,虽然ji的引用,类型为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包含多个元素,直接导致编译失败。x4x1一样被推导为std::initializer_list<int>x5被推导为单元素的类型int

2 decltype 关键字

decltype 是“declare type”的缩写,译为“声明类型”,它的作用是检查实体的声明类型或表达式的类型和值类别。

说到检查类型,很多同学肯定会想到typeid ,但是可惜的是它是在运行期(RTTI)确定对象类型信息,但是很多场景下,我们希望编译期就将对象类型信息确定,所以C++ 11引入了decltype

decltype(e)(其中e的类型为T)的推导规则有如下5条:

  1. 如果e是一个未加括号的标识符表达式(结构化绑定除外)或者未加括号的类成员访问,则decltype(e)推断出的类型是e的类型T。如果并不存在这样的类型,或者e是一组重载函数,则无法进行推导。

  2. 如果e是一个函数调用或者仿函数调用,那么decltype(e)推断出的类型是其返回值的类型。

  3. 如果e是一个类型为T的左值,则decltype(e)T&

  4. 如果e是一个类型为T的将亡值,则decltype(e)T&&

  5. 除去以上情况,则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规则4foo函数的返回类型是const int&&,所以推导结果也为const int&&decltype(i)decltype(a->x)很简单,满足规则1,所以其类型为intdouble;最后一句代码,由于decltype((a->x))推导的是一个带括号的表达式(a->x),因此规则1不再适用,但很明显a->x是一个左值,又因为a带有const限定符,所以其类型被推导为const double&

cv限定符的推导规则:

通常情况下,decltype(e)所推导的类型会同步ecv限定符,比如:

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类型,如果想在代码中改变ax的值,则肯定会编译失败。但是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限定符会同步到推断结果。

0

评论区