auto decltype

auto 和 decltype

语法格式的区别

auto 和 decltype 都是 C++11 新增的关键字,都用于自动类型推导,但是它们的语法格式是有区别的,如下所示:

auto varname = value; //auto的语法格式
decltype(exp) varname [= value]; //decltype的语法格式

其中,varname 表示变量名,value 表示赋给变量的值,exp 表示一个表达式,方括号[ ]表示可有可无。

auto 和 decltype 都会自动推导出变量 varname 的类型:

  • auto 根据=右边的初始值 value 推导出变量的类型;
  • decltype 根据 exp 表达式推导出变量的类型,跟=右边的 value 没有关系。

另外,auto 要求变量必须初始化,也就是在定义变量的同时必须给它赋值;而 decltype 不要求,初始化与否都不影响变量的类型。这很容易理解,因为 auto 是根据变量的初始值来推导出变量类型的,如果不初始化,变量的类型也就无法推导了。

auto 将变量的类型和初始值绑定在一起,而 decltype 将变量的类型和初始值分开;虽然 auto 的书写更加简洁,但 decltype 的使用更加灵活。

请看下面的例子:

auto n1 = 10;
decltype(10) n2 = 99;
auto url1 = "http://c.biancheng.net/cplus/";
decltype(url1) url2 = "http://c.biancheng.net/java/";
auto f1 = 2.5;
decltype(n1*6.7) f2;

这些用法在前面的两节中已经进行了分析,此处就不再赘述了。

对 cv 限定符的处理

「cv 限定符」是 const 和 volatile 关键字的统称:

  • const 关键字用来表示数据是只读的,也就是不能被修改;
  • volatile 和 const 是相反的,它用来表示数据是可变的、易变的,目的是不让 CPU 将数据缓存到寄存器,而是从原始的内存中读取。

在推导变量类型时,auto 和 decltype 对 cv 限制符的处理是不一样的。decltype 会保留 cv 限定符,而 auto 有可能会去掉 cv 限定符。

以下是 auto 关键字对 cv 限定符的推导规则:

  • 如果表达式的类型不是指针或者引用,auto 会把 cv 限定符直接抛弃,推导成 non-const 或者 non-volatile 类型。
  • 如果表达式的类型是指针或者引用,auto 将保留 cv 限定符。
auto 忽略顶层const ,保留底层const
decltype 则全部保留

下面的例子演示了对 const 限定符的推导:

//非指针非引用类型
const int n1 = 0;
auto n2 = 10;
n2 = 99;  //赋值不报错
decltype(n1) n3 = 20;
n3 = 5;  //赋值报错
//指针类型
const int *p1 = &n1;
auto p2 = p1;
*p2 = 66;  //赋值报错
decltype(p1) p3 = p1;
*p3 = 19;  //赋值报错

在 C++ 中无法将一个变量的完整类型输出,我们通过对变量赋值来判断它是否被 const 修饰;如果被 const 修饰那么赋值失败,如果不被 const 修饰那么赋值成功。虽然这种方案不太直观,但也是能达到目的的。

n2 赋值成功,说明不带 const,也就是 const 被 auto 抛弃了,这验证了 auto 的第一条推导规则。p2 赋值失败,说明是带 const 的,也就是 const 没有被 auto 抛弃,这验证了 auto 的第二条推导规则。

n3 和 p3 都赋值失败,说明 decltype 不会去掉表达式的 const 属性。

对引用的处理

当表达式的类型为引用时,auto 和 decltype 的推导规则也不一样;

decltype 会保留引用类型,而 auto 会抛弃引用类型,直接推导出它的原始类型。

请看下面的例子:

#include <iostream>
using namespace std;
int main() {
    int n = 10;
    int &r1 = n;
    //auto推导
    auto r2 = r1;
    r2 = 20;
    cout << n << ", " << r1 << ", " << r2 << endl;
    //decltype推导
    decltype(r1) r3 = n;
    r3 = 99;
    cout << n << ", " << r1 << ", " << r3 << endl;
    return 0;
}

运行结果:
10, 10, 20
99, 99, 99

从运行结果可以发现,给 r2 赋值并没有改变 n 的值,这说明 r2 没有指向 n,而是自立门户,单独拥有了一块内存,这就证明 r 不再是引用类型,它的引用类型被 auto 抛弃了。

给 r3 赋值,n 的值也跟着改变了,这说明 r3 仍然指向 n,它的引用类型被 decltype 保留了。

decltype 推导规则

decltype 推导规则

上面的例子让我们初步感受了一下 decltype 的用法,但你不要认为 decltype 就这么简单,它的玩法实际上可以非常复杂。当程序员使用 decltype(exp) 获取类型时,编译器将根据以下三条规则得出结果:

  • 如果 exp 是一个不被括号( )包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致,这是最普遍最常见的情况。
  • 如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。
  • 如果 exp 是一个左值,或者被括号( )包围,那么 decltype(exp) 的类型就是 exp 的引用;假设 exp 的类型为 T,那么 decltype(exp) 的类型就是 T&。

【实例1】exp 是一个普通表达式:

#include <string>
using namespace std;
class Student{
public:
    static int total;
    string name;
    int age;
    float scores;
};
int Student::total = 0;
int  main(){
    int n = 0;
    const int &r = n;
    Student stu;
    decltype(n) a = n;  //n 为 int 类型,a 被推导为 int 类型
    decltype(r) b = n;     //r 为 const int& 类型, b 被推导为 const int& 类型
    decltype(Student::total) c = 0;  //total 为类 Student 的一个 int 类型的成员变量,c 被推导为 int 类型
    decltype(stu.name) url = "http://c.biancheng.net/cplus/";  //total 为类 Student 的一个 string 类型的成员变量, url 被推导为 string 类型
    return 0;
}

【实例2】exp 为函数调用:

需要注意的是,exp 中调用函数时需要带上括号和参数,但这仅仅是形式,并不会真的去执行函数代码。

//函数声明
int& func_int_r(int, char);  //返回值为 int&
int&& func_int_rr(void);  //返回值为 int&&
int func_int(double);  //返回值为 int
const int& fun_cint_r(int, int, int);  //返回值为 const int&
const int&& func_cint_rr(void);  //返回值为 const int&&
//decltype类型推导
int n = 100;
decltype(func_int_r(100, 'A')) a = n;  //a 的类型为 int&
decltype(func_int_rr()) b = 0;  //b 的类型为 int&&
decltype(func_int(10.5)) c = 0;   //c 的类型为 int
decltype(fun_cint_r(1,2,3))  x = n;    //x 的类型为 const int &
decltype(func_cint_rr()) y = 0;  // y 的类型为 const int&&

【实例3】exp 是左值,或者被( )包围:

using namespace std;
class Base{
public:
    int x;
};
int main(){
    const Base obj;
    //带有括号的表达式
    decltype(obj.x) a = 0;  //obj.x 为类的成员访问表达式,符合推导规则一,a 的类型为 int
    decltype((obj.x)) b = a;  //obj.x 带有括号,符合推导规则三,b 的类型为 int&。
    //加法表达式
    int n = 0, m = 0;
    decltype(n + m) c = 0;  //n+m 得到一个右值,符合推导规则一,所以推导结果为 int
    decltype(n = n + m) d = c;  //n=n+m 得到一个左值,符号推导规则三,所以推导结果为 int&
    return 0;
}

这里我们需要重点说一下左值和右值:左值是指那些在表达式执行结束后依然存在的数据,也就是持久性的数据;右值是指那些在表达式执行结束后不再存在的数据,也就是临时性的数据。有一种很简单的方法来区分左值和右值,对表达式取地址,如果编译器不报错就为左值,否则为右值。

decltype 的实际应用

auto 的语法格式比 decltype 简单,所以在一般的类型推导中,使用 auto 比使用 decltype 更加方便,你可以转到《C++ auto》查看很多类似的例子,本节仅演示只能使用 decltype 的情形。

我们知道,auto 只能用于类的静态成员,不能用于类的非静态成员(普通成员),如果我们想推导非静态成员的类型,这个时候就必须使用 decltype 了。下面是一个模板的定义:

#include <vector>
using namespace std;
template <typename T>
class Base {
public:
    void func(T& container) {
        m_it = container.begin();
    }
private:
    typename T::iterator m_it;  //注意这里
};
int main()
{
    const vector<int> v;
    Base<const vector<int>> obj;
    obj.func(v);
    return 0;
}

单独看 Base 类中 m_it 成员的定义,很难看出会有什么错误,但在使用 Base 类的时候,如果传入一个 const 类型的容器,编译器马上就会弹出一大堆错误信息。原因就在于,T::iterator并不能包括所有的迭代器类型,当 T 是一个 const 容器时,应当使用 const_iterator。

要想解决这个问题,在之前的 C++98/03 版本下只能想办法把 const 类型的容器用模板特化单独处理,增加了不少工作量,看起来也非常晦涩。但是有了 C++11 的 decltype 关键字,就可以直接这样写:

template <typename T>
class Base {
public:
    void func(T& container) {
        m_it = container.begin();
    }
private:
    decltype(T().begin()) m_it;  //注意这里
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁金金

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值