本篇文章为笔者的读书笔记,未经允许请勿转载。如果对你有帮助记得点个赞(●’◡’●)
这篇文章是上篇文章《C++prvalue,xvalue和lvalue的相关知识》的续作,上次我们已经把prvalue,xvalue和lvalue说清楚了,本篇文章就来探讨一下prvalue,xvalue和lvalue与decltype之间的联系。顺便咱们也把auto类型说明符也都拓展一下。
C++11包括声明一个类型是从其初始化器推导出变量类型的能力(auto)。它还提供了一种机制来表示已命名对象(变量或函数)或表达式的类型(decltype)。这些设施原来非常方便,而C++14和C++17在这个主题上增加了额外的变体。
编程时常常需要把表达式的值赋给变量,这就要求在声明变量的时候清楚地知道表达式的类型。c++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。
//由val1和val2相加的结果可以推断出item的类型 auto item = val1 + val2; // item初始化为val1和val2相加的结果此处编译器将根据
val1
和val2
相加的结果来推断item
的类型。如果val1
和val2
是类sales_item
的对象,则item
的类型就是sales_item
;如果这两个变量的类型是double
,则item
的类型就是double
,以此类推。
编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。
首先,正如我们所熟知的,使用引用其实是使用引用的对象,特别是当引用被用作初始值时,真正参与初始化的其实是引用对象的值。此时编译器以引用对象的类型作为auto的类型:
int i = 0,&r = i; auto a = r; // a的类型为int(r是i的别名,而i是一个整数)其次,auto一般会忽略掉
顶层const
,同时底层const
则会保留下来,比如当初始值是一个指向常量的指针时:const int ci = i, &cr = ci; auto b = ci;// b的类型为int(ci的顶层const 特性被忽略掉了) auto c = cr;// c的类型为int( cr是ci的别名,ci本身是一个顶层const ) auto d = &i;// d是一个整型指针(int *) auto e = &ci;// e是一个指向整数常量的指针(const int *)(对常量对象取完地址后,常量对象的顶层const在auto处转变为底层const)如果希望推断出的auto类型是一个
顶层const
,需要明确指出:const auto f = ci; // ci的推演类型是int,f是const int还可以将引用的类型设为auto,此时原来的初始化规则仍然适用:
auto &g = ci; // g是一个整型常量引用,绑定到ci auto &h = 42; //错误:不能为非常量引用绑定字面值(42的类型为int,左值引用不能绑定到右值) const auto &j=42; //正确:可以为常量引用绑定字面值(42发生临时物化,产生一个xvalue的临时对象让常量引用绑定)设置一个类型为auto的引用时,初始值中的顶层常量属性仍然保留。和往常一样,如果我们给初始值绑定一个引用(
auto &g = ci;
),则此时的const就不是顶层const
了(g的类型为const int &
,此处的const为底层const
)。
要在一条语句中定义多个变量,切记,符号&
和*
只从属于某个声明符,而非基本数据类型的一部分,因此初始值必须是同一种类型:auto k = ci, &l = i;// k是int,l是int& auto &m = ci,*p = &ci;// m是对整型常量的引用(const int &),p是指向整型常量的指针(const int *)。 auto &n= i, *p2 = &ci;//错误:i的类型是int而&ci的类型是const int以上部分为auto类型说明符的基础部分,大部分例子为《c++primer》p61页的,我在其基础上添加了注释以及对其进行部分修改,以便读者能快速领悟其含义。其他基础部分若有不懂请自行补充,不再赘述。
auto类型说明符可用于许多地方(主要是namespace作用域和local作用域),以从其初始化器推导变量的类型。在这种情况下,auto被称为占位符类型(a placeholder type)(另一种占位符类型
deltype(auto)
,文章后面将会描述)。例如,您可以使用:template<typename Container> void useContainer(Container const& container) { auto pos = container.begin(); //auto示例一 while (pos != >container.end()) { auto& element = *pos++; //auto示例二 … // operate on the element } }上面示例中auto的两种使用消除了编写两种长且可能复杂的类型,即容器的迭代器类型和迭代器的值类型:
typename Container::const_iterator pos = container.begin(); … typename std::iterator_traits<typename Container::iterator>::reference element = *pos++;自动推导使用与模板参数推导相同的机制。auto类型说明符可以被一个虚构的模板类型参数
T
取代,然后推导继续进行,就好像该变量是一个函数形参,它的初始化器相当于函数实参。对于第一个auto示例,它对应于以下情况:template<typename T> void deducePos(T pos); deducePos(container.begin());这里
T
看作auto
,是要被推导出的类型。这样做的直接后果之一是,auto类型的变量永远不会是引用类型。
在第二个auto示例中使用auto&
说明了如何生成一个引用类型的推导。其推导相当于以下函数模板和调用:template<typename T> deduceElement(T& element); deduceElement(*pos++);在这里,
element
总是引用类型,并且它的初始化器不能生成一个临时对象。
也可以将auto与右值引用组合起来,但这样做使其行为像一个转发的引用(a forwarding reference),例如:
auto&& fr = …;
我们还是基于函数模板来看待它:template<typename T> void f(T&& fr);// auto replaced by template parameter T解释如下:
int x; auto&& rr = 42; // OK: rvalue reference binds to an rvalue 个(auto = int) auto&& lr = x; // Also OK:reference collapsing makes. lr an lvalue reference 个(auto = int&)这种技术经常用于代码中绑定函数或操作符调用的结果对象,且不知道结果对象的值类别(lvalue vs.rvarue),进而不必复制该结果对象。
例如,它通常是在基于范围的循环中声明迭代值的首选方法:template<typename Container> void g(Container c) { for (auto&& x: c) { … } }这里我们不知道容器迭代接口的签名( the signatures of the container’s iteration interfaces),但是通过使用
auto&&
,我们可以确信我们正在遍历的值没有产生额外的副本。如果需要完美转发绑定值,则可以像往常一样在变量上调用std::forward<T>()
。这使得一种“延迟”的完美转发成为可能。有关示例,请参见《c++ template 2nd》p167。
除了引用之外,还可以组合auto说明符来创建const对象、指针、成员指针等等,但auto必须声明成“main”类型说明符(基本数据类型)。它不能嵌套在模板参数中或跟在基本数据类型后面的声明部分中( part of the declarator that follows the type specifier)。具体请看下面的示例:template<typename T> struct X { T const m; }; auto const N = 400u; // OK: constant of type auto* gp = (void*)nullptr; // OK: gp has type void* auto const S::*pm = &X<int>::m; // OK: pm has type int const X<auto> xa = X<int>(); // ERROR: auto in template int const auto::*pm2 = &X<int>::m; // ERROR: auto is >part of the “declarator”最后两个例子不让通过的原因在于C++委员会认为,额外的实施成本和滥用潜力超过了好处