C&C++   发布时间:2022-04-03  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了C++ decltype(类型推导)精讲大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
学习了《C++ auto》一节我们应该知道,auto 用于通过@L_450_1@表达式在编译时确定待定义的变量类型,auto 所修饰的变量必须被初始化,编译器需要通过初始化来确定 auto 所代表的类型,即必须要定义变量。若仅希望得到类型,而不需要(或不能)定义变量的时候应该怎么办呢?

C++11 新增了 decltype 关键字,用来在编译时推导出@L_450_1@表达式的类型。它的语法格式如下:

decltype(exp)

其中,exp 表示@L_450_1@表达式(expression)。

从格式上来看,decltype 很像 sizeof ——用来推导表达式类型大小的操作符。类似于 sizeof,decltype 的推导过程是在编译期完成的,并且不会真正计算表达式的值。

那么怎样使用 decltype 来得到表达式的类型呢?让我们来看一组例子:
int x = 0;
decltype(X) y = 1;           // y -> int
decltype(x + y) z = 0;       // z -> int
const int& i = x;
decltype(i) j = y;           // j -> const int &
const decltype(z) * p = &z;  // *p  -> const int,p  -> const int *
decltype(z) * pi = &z;       // *pi -> int,pi -> int *
decltype(pi)* pp = π      // *pp -> int *,pp -> int * *
代码的说明:
1) y 和 z 的结果表明 decltype 可以根据表达式直接推导出它的类型本身。这个功能和上一节的 auto 很像,但又有所不同。auto 只能根据变量的初始化表达式推导出变量应该具有的类型。若想要通过某个表达式得到类型,但不希望新变量和这个表达式具有同样的值,此时 auto 就显得不适用了。

2) j 的结果表明 decltype 通过表达式得到的类型,可以保留住表达式的引用及 const 限定符。实际上,对于一般的标记符表达式(id-expression),decltype 将精确地推导出表达式定义本身的类型,不会像 auto 那样在某些情况下舍弃掉引用和 cv 限定符。

3) p、pi 的结果表明 decltype 可以像 auto 一样,加上引用和指针,以及 cv 限定符。

4) pp 的推导则表明,当表达式是@L_450_1@指针的时候,decltype 仍然推导出表达式的实际类型(指针类型),之后结合 pp 定义时的指针标记,得到的 pp 是@L_450_1@二维指针类型。这也是和 auto 推导不同的一点。

对于 decltype 和引用(&)结合的推导结果,与 C++11 中新增的引用折叠规则(Reference Collapsing)有关,因此,留到后面右值引用(Rvalue Reference)一节再详细讲解。

扩展阅读

关于 p、pi、pp 的推导,有个很有意思的地方。像 Microsoft Visual studio 这样的 IDE,可以在运行时观察每个变量的类型。我们可以看到 p 的显示是这样的

这其实是 C/C++ 的@L_450_1@违反常理的地方:指针(*)、引用(&)属于说明符(declarators),在定义的时候,是和变量名,而不是类型标识符(type-specifiers)相结合的。

因此,const decltype(z)*p推导出来的其实是 *p 的类型(const int),然后再进一步运算出 p 的类型。

decltype 的推导规则

从前面的内容来看,decltype 的使用是比较简单的。但在简单的使用方法之后,也隐藏了不少细节。

我们先来看看 decltype(exp) 的推导规则:
  • 推导规则1,exp 是标识符、类访问表达式,decltype(exp) 和 exp 的类型一致。
  • 推导规则2,exp 是函数调用,decltype(exp) 和返回值的类型一致。
  • 推导规则3,其他情况,若 exp 是@L_450_1@左值,则 decltype(exp) 是 exp 类型的左值引用,否则和 exp 类型一致。
只看上面的推导规则,很难理解decltype(exp) 到底是@L_450_1@什么类型。为了更好地讲解这些规则的适用场景,下面根据上面的规则分3种情况依次讨论:
  • 标识符表达式和类访问表达式。
  • 函数调用(非标识符表达式,也非类访问表达式)。
  • 带括号的表达式和加法运算表达式(其他情况)。

1) 标识符表达式和类访问表达式

先看第一种情况,下面的代码是一组简单的例子

【实例】decltype 作用于标识符和类访问表达式示例。
class Foo
{
    public:
    static const int number = 0;
    int x;
};
int n = 0;
volatile const int & x = n;
decltype(n) a = n;            // a -> int
decltype(X) b = n;            // b -> const volatilE int &
decltype(Foo::number) c = 0;  // c -> const int
Foo foo;
decltype(foo.X) d = 0;        // d -> int,类访问表达式
变量 a、b、c 保留了表达式的所有属性(cv、引用)。这里的结果是很简单的,按照推导规则1,对于标识符表达式而言,decltype 的推导结果就和这个变量的类型定义一致。

d 是@L_450_1@类访问表达式,因此也符合推导规则1。

2) 函数调用

接下来,虑第二种情况:如果表达式是@L_450_1@函数调用(不符合推导规则1),结果会如何呢?请看下面的代码

【实例】decltype 作用于函数调用的示例。
int& func_int_r(void);           // 左值(lvalue,可简单理解为可寻址值)
int&& func_int_rr(void);         // x值(xvalue,右值引用本身是@L_450_1@xvalue)
int func_int(void);              // 纯右值(prvalue,将在后面的章节中讲解)
const int& func_cint_r(void);    // 左值
const int&& func_cint_rr(void);  // x值
const int func_cint(void);       // 纯右值
const Foo func_cfoo(void);       // 纯右值
// 下面是测试语句
int x = 0;
decltype(func_int_r())   a1 = x;      // a1 -> int &
decltype(func_int_rr())  b1 = 0;      // b1 -> int &&
decltype(func_int())     c1 = 0;      // c1 -> int
decltype(func_cint_r())  a2 = x;      // a2 -> const int &
decltype(func_cint_rr()) b2 = 0;      // b2 -> const int &&
decltype(func_cint())    c2 = 0;      // c2 -> int
decltype(func_cfoo())    ff = Foo();  // ff -> const Foo
可以看到,按照推导规则2,decltype 的结果和函数的返回值类型保持一致。

这里需要注意的是,c2 是 int 而不是 const int。这是因函数返回的 int 是@L_450_1@纯右值(prvalue)。对于纯右值而言,只有类类型可以携带 cv 限定符,此外则一般忽略掉 cv 限定。

如果在 gcc 下编译上面的代码,会得到@L_450_1@警告信息如下:

warning: type qualif?iers ignored on function return type [-Wignored-qualifiers] cint func_cint(void);

因此,decltype 推导出来的 c2 是@L_450_1@ int。

作为对比,可以看到 decltype 根据 func_cfoo() 推导出来的 ff 的类型是 const Foo。

3) 带括号的表达式和加法运算表达式

最后,来看看第三种情况:
struct Foo { int x; };
const Foo foo = Foo();
decltype(foo.X)   a = 0;  // a -> int
decltype((foo.X)) b = a;  // b -> const int &
int n = 0,m = 0;
decltype(n + m) c = 0;    // c -> int
decltype(n += m) d = c;   // d -> int &
a 和 b 的结果:仅仅多加了一对括号,它们得到的类型却是不同的。

a 的结果是很直接的,根据推导规则1,a 的类型就是 foo.x 的定义类型。

b 的结果并不适用于推导规则1和2。根据 foo.x 是@L_450_1@左值,可知括号表达式也是@L_450_1@左值。因此可以按照推导规则3,知道 decltype 的结果将是@L_450_1@左值引用。

foo 的定义是 const Foo,所以 foo.x 是@L_450_1@ const int 类型左值,因此 decltype 的推导结果是 const int&。

同样,n+m 返回@L_450_1@右值,按照推导规则3,decltype 的结果为 int。

最后,n+=m 返回@L_450_1@左值,按照推导规则3,decltype 的结果为 int&。

decltype 的实际应用

decltype 的应用多出现在泛型编程中,请看下面的例子。

【实例】泛型类型定义可能存在问题的示例。
#include <vector>
template <class ContainerT>
class Foo
{
    typename ContainerT::iterator it_; // 类型定义可能有问题
public:
    void func(ContainerT& container)
    {
        it_ = container.begin();
    }
    // ...
};
int main(void)
{
    typedef const std::vector<int> container_t;
    container_t arr;
    Foo<container_t> foo;
    foo.func(arr);
    return 0;
}
单独看类 Foo 中的 it_ 成员定义,很难看出会有什么错误,但在使用时,若上下文要求传入@L_450_1@ const 容器类型,编译器马上会弹出一大堆错误信息。

原因就在于,ContainerT::iterator 并不能包括所有的迭代器类型,当 ContainerT 是@L_450_1@ const 类型时,应当使用 const_iterator。

要想解决这个问题,在 C++98/03 下只能想办法把 const 类型的容器用模板特化单独处理,比如增加@L_450_1@像下面这样的模板特化:
template <class ContainerT>
class Foo<const containerT>
{
    typename ContainerT::const_iterator it_;
public:
    void func(const containerT& container)
    {
        it_ = container.begin();
    }
    // ...
};
这实在不能说是@L_450_1@好的解决办法。若 const 类型的特化只是为了配合迭代器的类型限制,Foo 的其他代码也不得不重新写一次。

有了 decltype 以后,就可以直接这样写:
template <class ContainerT>
class Foo
{
    decltype(ContainerT().begin()) it_;
public:
    void func(ContainerT& container)
    {
        it_ = container.begin();
    }
    // ...
};
是不是舒服很多了?

decltype 也经常用在通过变量表达式抽取变量类型上,如下面的这种用法
vector<int> v;
// ...
decltype(v)::value_type i = 0;
在冗长的代码中,人们往往只会关心变量本身,而并不关心它的具体类型。比如在上例中,只要知道v是@L_450_1@容器就够了(可以提取 value_type),后面的所有算法内容只需要出现 v,而不需要出现像vector<int> 这种精确的类型名称。这对理解一些变量类型复杂但操作统一的代码片段有很大好处。

实际上,标准库中有些类型都是通过 decltype 来定义的:
typedef decltype(nullptr) nullptr_t;  //通过编译器关键字nullptr定义类型nullptr_t
typedef decltype(sizeof(0)) size_t;
这种定义方法的好处是,从类型的定义过程上就可以看出来这个类型的含义。

大佬总结

以上是大佬教程为你收集整理的C++ decltype(类型推导)精讲全部内容,希望文章能够帮你解决C++ decltype(类型推导)精讲所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。
标签: