2.auto、decltype和decltype(auto)的用法
1.auto
编程时常常需要把表达式的值赋给变量,这就要求声明变量时清楚的知道表达式的类型。然而有些情况是声明的变量的类型我们并不知道,比如在模板编程时。为了解决这个问题,C++11引入了auto类型说明符,用它来让编译器替我们去分析表达式所属的类型。
1.1auto的推导规则
规则1:声明为auto(不是auto&)的变量,忽视掉初始化表达式的顶层const。即对有const的普通类型(int 、double等)忽视const,对常量指针(顶层const)变为普通指针,对指向常量(底层const)的常量指针(顶层const)变为指向常量的指针(底层const)。
规则2:声明为auto&的变量,保持初始化表达式的顶层const或volatile 属性。
规则3:若希望auto推导的是顶层const,加上const,即const auto。
关于顶层和底层const是什么,可参考68.C++中的const - CodeMagicianT - 博客园 (cnblogs.com)
1.2auto
(1) auto例子
int i = 0, &ri = i;
auto a = i; //a为int型变量
auto a1 = ri; //a1为int型变量
auto p = &i;// &i 是一个普通int指针,p是一个整型指针int *
auto p1 = &ri; //同上
const int ci = 2, &rci = ci , ci2 = 9;
auto b = ci;//b为int型变量,因为规则1,b`并不是一个const int型的常量`
auto b1 = rci;//同上
b = 4;b1 = 5;//b和b1的值可以改变
auto cp = &ci;//cp是一个指向常量的指针const int* ,因为&ci对常量对象取地址是底层const,无顶层const属性
cp = &ci2;//cp的指向可以改变
下面看忽视顶层const指针的例子:
int z = 9,z1 = 10;
int* const pz1 = &z;//pz1为int* const(顶层const)
const int* pz2 = &z;//pz2为const int* (底层const)
const int* const pz3= &z;//pz3为const int* const(同时包含底层和顶层const)
auto apz1 = pz1;//apz1为int*
auto apz2 = pz2;//apz2为const int*
auto apz3 = pz3;//apz3为const int*
对于pz1和pz3,它们都有顶层const属性,所以apz1和apz3都会忽略顶层const属性。
这里注意:对常量对象取地址总是看作为一种底层的const,即不是对指针(也就是指向)的常量,而是对指针指向内存中的值是常量
。所以上面的&ci是const int*型。
(2)const auto例子
int i = 0, &ri = i;
const int ci = 2, &rci = ci ;
const auto cb = i; //cb为const int型。因为规则3,cb被提升为const
const auto cb1 = ci; //同上
const auto ca1 = &i;//cal为常量指针。&i本是int*,因为规则3,强行将cal提升为常量指针int *const
const auto ccp = &ci;//本来&ci为const int *,因为规则3,加了const后,提示为const int * const
1.3声明为auto引用:auto&
例子:
int i = 0, &ri = i;
const int ci = 2, &rci = ci ;
auto & j = i; //j为int &
auto & k = ci; // k为const int &
auto & h = 42; //错误,不能将非常量引用绑定字面值,这是引用&规则决定的
const auto &j2 = i; //j2为const int &,因为规则3,j2被提升为顶层const
const auto &k2 = ci; //k2为const int &
const auto &h2 = 42; //正确,可以为常量绑定字面值
auto& m = &i;//Error,无法从“int *”转换为“int *&” ,这是引用&规则决定的
auto& m1 = &ci;// Error,无法从“const int *”转换为“const int *&” ,这是引用&规则决定的
const auto &m2 = &i;//m2为int * const &
const auto &m3 = &ci;//m3为const int * const &
上例子中有3条是错误的,原因是:引用不能绑定表达式的计算结果,除非使用const。并且对于 普通指针而言,它提升到顶层const,只能为常量指针,而不能为指向常量的指针 (原因可参考:为什么无法从“int *”转换为“const int *&”?)。所以后两个都带常量指针属性,最后一个由于本身就是底层的const(即指向常量的指针),所以为指向常量的常量指针。
有关对于指针的引用,我在无法从“int *”转换为“int *&”?详解C++引用(&)使用方法中有详细的介绍,可供大家参考。
有关常量指针和指向常量的指针,我在详解const引用和常量指针、指向常量的指针有一小节详细介绍,可供大家参考。
小结:使用auto &的时候不光需要知道auto&的推到规则,还要明白引用(&)的使用限制。我们首先看的就是&的使用限制。
1.4auto在编程时真正的用途
上面我们详细介绍了auto的使用规则,但它仅仅是使用规则而已,在实际编程中我们在明确变量类型情况下,还是使用明确的类型。Auto真正在编程时的实际应用如下:
内容参考至:https://www.cnblogs.com/QG-whz/p/4951177.html
(1)代替冗长复杂的变量声明
我们在使用迭代器时常常会这样操作:
list<int> l1;
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
for (list<int>::iterator i = l1.begin(); i != l1.end(); i++)
{
cout << i.operator->() << endl;
cout << *i << endl;
}
这样list<int>::iterator i = l1.begin()
的声明迭代器i看起来繁琐冗长,我们实际可以用auto代替:auto i = l1.begin();
(2)定义模板参数时,用于声明依赖模板参数的变量
template <typename _Tx,typename _Ty>
void Multiply(_Tx x, _Ty y)
{
auto v = x+y;
std::cout << v;
}
如上所示:我们获取x+y的值,但是x、y都是模板类型,我们无法知道其类型,这时就可以使用auto。
(3)模板函数依赖于模板参数的返回值
template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)->decltype(x*y)
{
return x*y;
}
上面的例子中,返回值依赖于xy的类型,这里我们需要提前查询xy的数据类型,需要用到decltype操作符,它是C++11标准引入的新的运算符,其目的也是解决泛型编程中有些类型由模板参数决定,而难以表示它的问题。
注意:auto在这里的作用也称为返回值占位,它只是为函数返回值占了一个位置
有关decltype,参考:有auto为什么还要decltype ?详解decltype的用法
2.decltype
auto和decltype推导类型的区别
既然auto可以推导变量的类型,为什么C++11还引进decltype类型说明符呢?关于这一点,C++ Primer中这样写道:有时希望从表达式的类型推断出要定义的变量的类型(这一点auto可以做到),但是不想用该表达式的值初始化变量(auto依赖这一点才能推导类型)。请看下面的例子:
int a = 10, b = 11;
auto c = a + b; //c为int型
decltype(a + b) d ; //d为int型
auto通过初始化它的表达式来推断c的类型,也就是说,auto推导变量依赖于初始化它的表达式,并且auto声明的变量必须初始化;而decltype是直接通过某一个表达式来获取数据类型,从而定义d的类型。
2.1decltype变量
形式:decltype(var)
和auto不同,decltype会保留const属性和引用属性,看下面的例子:
const int ci = 0, &cj = ci;
decltype(ci) x = 0;//x的类型为const int
decltype(cj) y = x; //y的类型为const int&
decltype(cj) z; //错误,因为z的类型为const int&,必须初始化
auto w = ci;//w的类型是int
w = 9;
auto n = cj;//n的类型是int
2.2decltype表达式
形式:decltype(expr)
decltype表达式时,返回的类型根据表达式的结果不同而不同:expr返回左值,得到该类型的左值引用;expr返回右值,得到该类型。
(1)表达式做右值
如下面的例子中:
尽管r是引用类型,但是r+0是一个具体的值,只能做右值,值对应的类型是int型,所以b为int类型。
int i = 42, &r = i;
decltype(r + 0) b; //b类型是int,而不是int&
(2)表达式能做左值
结论:表达式能做左值,推导为类型的引用。
表达式能做左值有两个典型的例子:decltype (*p)和decltype ((ii))。请看下面的例子:
●对于解引用*p, 它代表的是p指向地址中的值,同时我们可以给这个值赋值,即为左值。所以,decltype(*p)是int& ,这样才能有给绑定变量的值赋值的特点。
●ii是一个变量,加上括号后变为表达式,即(ii)是一个表达式,又我们可以ii赋值,即为左值。所以,decltype((var))永远是一个引用类型,decltype((ii))声明变量d时,d就为int&类型。
int ii = 42, *p = ⅈ
decltype(*p) c;//错误,c是int&,必须初始化
decltype((ii)) d;//错误,d是int&,必须初始化
2.3 decltype 函数
(1)decltype(f())
直接看下面的例子:
decltype(f()) sum = x;
其中,sum的类型就是函数f的返回类型,sum的类型就是假如函数f被调用,它会返回那个类型。注意:若是函数f的返回值为void,编译报错
。
再看下面的例子:
m的类型为int型;m2的类型为double型。
template <typename T>
T add(T a, T b)
{
return a+b;
}
decltype(add(1,2)) m = 10; //m的类型是int
decltype(add(1.0,2.0)) m2 = 20; //m2的类型是double
(2)decltype(f)
看下面的例子,decltype(add_to)直接返回函数类型,所以pf是一个函数指针。
int add_to(int a, int b)
{
return a + b;
}
decltype(add_to) *pf = add_to; //pf就是一个函数指针,类型为int (int,int)
pf(1,2);
那么可以返回模板函数的函数指针吗?如下,显然是不行的,因为模板函数依赖于参数列表,只根据函数名是无法推断函数类型的,所以说函数指针pf的类型无法确认
。
template <typename T>
T add_to(T a, T b)
{
return a + b;
}
decltype(add_to) *pf = add_to;
pf(1,2);
和模板函数一样,如果函数是重载的,也无法通过函数名来推断返回的函数类型,那么也无法返回函数指针,如下面的例子中声明pf为函数指针是错误的。
int add_to(int a, int b)
{
return a + b;
}
int add_to(int a, int b,int c)
{
return a + b +c;
}
decltype(add_to) *pf = add_to;
pf(1,2);
C++ 11 中decltype的主要作用
Decltype在C++11中的主要作用是用于申明返回值类型依赖于其参数类型的模板函数。例子如下:
template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)->decltype(x*y)
{
return x*y;
}
注意这里的auto并没有做任何类型推断(关于auto的用法:参考C++ auto用法及应用详解),只是用来表明这里使用的是C++11 的拖尾返回类型(trailing return type)语法,也就是函数返回类型将在参数列表之后进行声明(在"->"之后),优点是可以使用函数参数来声明函数返回类型(如果将返回类型放置于函数之前,这里的参数x和y还没有被声明,因此不能被使用)。
参考:有auto为什么还要decltype ?详解decltype的用法