主要讲讲编译时和运行时类型推导的用处和妙用, 一般是在模板编程中.
摘要: auto&decltype 表面上是为了实现C++11的新特性才引入的, 实际上编程中使用它会大大简化代码.
最常见的情况恐怕是写vector<int>::iterator it
, 这种名字过长的情景, 简化了代码的书写.
auto
编译时类型推导
(C++98/03中一直就有auto关键字了,只不过在C++98/03中auto关键字用于标识具有自动存储期的局部变量,它的作用并不大; 在C++11中auto关键字被重新利用了,用作类型推导)
写容器代码的时候, 尤其是迭代器, 算法之类的, 太长了&复杂了, 直接用auto吧, 编译器自动推导类型
比如:
1 | auto x = 5; //auto即int |
还有一种情况用auto, 即
需要给lambda表达式命名的时候, 匿名的仿函数, 很少能写出它的命名, 直接用auto吧.
《深入应用C++11 代码优化与工程机应用》
专门研究过这个问题.
推导规则
理论上, 这里属于编译器实现的部分, 编译器会根据具体的情况推导出具体的类型.
- 当表达式是一个引用类型的时候,auto会把引用类型抛弃,直接推导成原始类型
- 当表达式是一个带cv限定符时,auto会把cv限定符也就是const抛弃,推导成non-const的
- 当auto和引用类型相结合时, 编译器在推导时保留表达式的cv限定符
总结起来就:
- 当不声明为指针和引用是,auto的推导结果类型将抛弃表达式的引用和CV限定符
- 当声明为指针或引用时,auto推导结果将保留表达式的的cv属性
1
2
3int x = 0;
const auto &g = x;
auto &h = g; //此时h的类型是 const int &, 即auto是保留了const属性
最好:
在传递const变量的时候,使用auto必须自己加const
auto不涉及引用和指针, 即&
和*
, 在定义的时候, 单独写出来
注意事项
注意一下, auto的机制限制:
- auto不能作为函数形参
- auto不能修饰类或者结构体中的非静态成员变量
- auto不能定义数组
- auto不能推导出模板参数
它毕竟是编译时处理, 即编译时不能确定的, auto都做不了(运行时需要确定的请用decltype, 获取变量&对象&表达式的类型)
具体的例子:
1 | void func(auto a = 1){}; //error: auto不能用于函数形参,哪怕是带默认参数的形参 |
高级用法
一般高级用法是用在模板, 即元编程中, 用于运行时才能确定类型, 当前先用auto为了通过编译, 需要auto
和decltype
配合.
例如, 某个方法, 由于某种原因, 其返回值当前不知道(取决于形参类型), 可以这么写:
1 | template<typename X, tyname Y> |
如果你直接写decltype(x+y) get(X x, Y y)
是不能通过编译的, 因为直到函数体执行时, 编译器才了解到 x 和 y.
decltype
decltype
有部分网友说这个关键词纯粹是为了开发标准库而引入的机制, 可能吧
C++11 也提供了从对象或表达式中“俘获”类型的机制, 相当于typeof()
, 但是不同于 auto, 它永远能推导出实际调用对象的类型, 即运行时推导.
decltype永远推导出表达式的实际类型,不管你定义时是不是加了指针或者引用标记(一般不需要额外配合, 也不丢失属性)
推导规则如下:
- expr是标识符,类访问表达式,decltype(expr)和expr的类型保持一致
- expr是函数调用,decltype(expr)和返回值类型一致
- expr是一个左值,则decltype(expr)是expr类型的左值引用,否则和expr类型一致
主要用途:
- 用于返回值类型
- 用于类型替代
- 元编程(模板编程)
返回值
新的操作符 decltype 可以从一个表达式中“俘获”其结果的类型并“返回”.
再重复一下是上面 auto 的例子, 在《C++标准库2e》
中有这么一个用法:
1 | template<typename T1, typename T2> |
用来描述两个类型兼容(当然不是类型一致, 类型一致的话, 要一个模板参数即可)的两个变量的加操作.
可惜编译不通过, 因为decltype(x+y)
时, 还不知道这两个变量, 只有在函数实现部分内部才知道.
所以编译器需要我们这样处理:
1 | template<typename T1, typename T2> |
表示编译时, 编译器当做auto即可, 运行时根据decltype进行推到(或强转).
类型替代
其实它就是 gnu实现的typeof
的标准版, 拿到类型, 然后用于下面的代码:
1 | map<string, float> m; |
再例如:
1 | const vector<int> vi; |
(不同于typeid
, 它的功能非常有限, 只用于显示类型)
元编程
还有一种情况, 其实就是倒腾模板, 比如说, lambda表达式的的类型需要传递给模板, 例如下面的代码:
1 | auto comp = [](const Person&p1, const Person&p2)->bool{ |
如果这里不传入comp, 即set1(comp);
不指定用于比较的 functor , 那么编译器就会报错, 找不到lambda表达式类型的functor的构造器以及赋值函数.
其他例子:
1 | template<typename T> |
其中typename
就是用来告诉编译器整个后面的decltype(obj)::iterator
或者T::iterator
确实是模板类型.
可能有人会觉得多此一举, 但是注意上面的, 模板第一编译的时候肯定通过, 而第二次编译, 即根据调用特化成模板函数时, 不一定成功,
因为
T::iterator
, 类型T必须要有iterator才行.
auto, decltype 对于元编程的内容的影响远不止如此, 水平有限, 先说这么多.
参考资料
- 《深入应用C++11 代码优化与工程机应用》
- 《C++标准库2e》