关于decltype获取表达式类型的问题以及decltype和引用

1.

int a =3;
decltype(f()) b =a;

书上说编译器分析表达式的值,却不实际计算表达式的值,编译器并不实际调用函数f,而是使用当调用发生时f的返回值类型作为b的类型。那如果代码中没有调用f,decltype是如何获取类型的呢?
比如代码:

#include<iostream>
using namespace std;
int f()
{
    return 1;
}
int main()
{
    int a = 3;
    decltype(f()) b = a;  //这里的decltype是如何获取表达式类型的?
    return 0;
}

2 .

decltype(*p) c;//错误,c是int&,必须被初始化

书上说“如果表达式的内容是解引用操作,则decltype将得到引用类型。解引用指针可以得到指针所指的对象,而且还能给
这个对象赋值。因此,decltype(*p)的结果类型就是int&,而非int
但是这个解释好像也没说明白为什么“c是int&”,int&声明的c可以被赋值,int声明的c也是可以赋值的 啊,为什么非要强调是int&呢?

ps. 参考的书籍是c++primer第五版(中文版)
以上问题在中文版page63,英文版page71附近。
恳求各位给予答疑,非常感谢!

阅读 8.6k
3 个回答

1) 你说为什么编译器是如何获取到类型的,首先你要了解声明的本质是什么?

declaration is a statement in a program that communicates this 
information to the compiler
.

直白的说,你的 f() 在声明的那一刻,编译器就已经记住了你所声明的类型。你用 decltype 的时候,仅仅是直接用了这个结果而已。

这个问题,超出了 C++ Primer 的范畴,你需要学习编译原理。

2) 显然,你没搞清楚 intint& 的差别。请回到 2.3.1 References 这一节。

A reference defines an alternative name for an object.
A reference must be initialized.

简单说,int& 是引用,引用是什么?别名。别名是什么?另一个名字。你必须先有一个对象,才能有另一个名字。所以,你可以写:

int i;

但是你不可以写:

int& ri; // error!!! a reference must be initialized.

回到你的问题。如果是 intdecltype(*p) c; 不会报错;如果是 int&,会报错。

所以强调。


@王子亭 提醒我题主可能是不清楚为何返回的是 int&, 这个可能看过书就比较清楚了,为了更清楚的描述题主的疑问,我将书上这部分代码在此补全:

cppint i = 42, *p = &i, &r = i;
decltype(r + 0) b; // ok
decltype(*p) c; // error: c is int& and must be initialized.

书上的解释:(抱歉,我只有英文版)

As we've seen, when we dereference a pointer, we get the object to which the pointer points. Moreover, we can assign to that object. Thus, the type deduced by decltype(*p) is int&, not plain int.

故,decltype(*p) <==> decltype(&i), 你明白这个即可。

据我所知,如果你只看到此书的这个地方,应该还不了解左值和右值的。所以,尽量不要想太多。


若你非要打破沙锅问到底,如 @王子亭 所言,C++ 11 标准(draft N3690)中是规定这个了的:

7.1.6.2/4
The type denoted by decltype(e) is defined as follows:

  • if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
  • otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
  • otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
  • otherwise, decltype(e) is the type of e.

The operand of the decltype specifier is an unevaluated operand (Clause 5).

最近看了这本书,看到这也疑惑了很久。最后理解了(或者说是记住了,之后信服这种解释了)。

如果你手头有书的话,可以看一下书中96页的表3.6,其中第一行有一个对迭代器解引用操作,虽然此处不是指针,但是原理一样。注意对迭代器iter解引用操作

*iter

得到的是iter所指元素的引用,这里相当于是一个强行解释。初学C++的人如果学过C,对这里的解释肯定很奇怪。因为在C中,没有引用这一概念,*iter得到的就是iter所指对象,而不是iter所指对象的引用。
但是在C++中,因为有了引用这一概念,再看书中63页对decltype(*p)的解释,
解引用指针可以得到指针所指的对象,而且还能给这个对象赋值
这是什么?其实就是一个匿名的引用,或者说

*p

本身就是对p所指对象的一个引用,对*p的操作,自然可以理解为对p所指对象的操作。


但是前面的这些,都只是我的一种理解方式。
在c++中,解引用操作得到是引用而非原值。
`int a;
int *p = &a;`
这里*p是a的引用,而非a对象本身。
这种方式对我理解这个概念是有些帮助的,但不一定完全正确。如果你想找关于左值,右值对于decltype的影响的解释,看书的121页下半页,有比较详尽的解释。

(没看过 C++ Primer)

  1. int f() 这里不是已经写了 f 的返回值是 int 类型嘛,编译时当然可以分析出 f() 的类型是 int.
    decltype 的性质就和 sizeof 是类似的,sizeof(f()) 也是可以在编译时求值的。

「如果 decltype 的对象是一个表达式且是一个左值,则结果是它的引用类型」这只是一个规定,我也说不太清楚设计者的逻辑,但可以看一下维基百科的这个页面,上面有一些比较细致的讨论 http://zh.wikipedia.org/wiki/Decltype

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题