我正在阅读c++入门,不太明白表达式何时产生对象类型,何时产生对象的引用类型。
我从书中引用:
- 当我们对一个不是变量的表达式应用decltype时,我们得到该表达式产生的类型。
一般来说,decltype为产生的表达式返回一个引用类型
考虑下面的代码:
int i = 3, *ptr = &i, &ref = i;
decltype(ref + 0) j;
在上面的代码中,表达式"ref + 0"导致了一个固有的操作,即对ref引用的对象i和0的值进行相加。因此,根据第一条规则,表达式产生int类型。但是根据第二条规则,由于表达式产生的对象类型可以位于赋值操作的左侧(在本例中为int),那么decltype不应该产生ref to int(int&)类型吗?
书上还说,对于下面的代码
decltype(*ptr) k;
k的类型为int&而不是int,即表达式生成的类型。
它还表示,对于像
下面代码中的赋值表达式decltype(a = b) l;
l应该在赋值操作的左侧有object的引用类型。
我们如何知道哪些表达式产生对象类型,哪些表达式产生对对象类型的引用?
不正式地理解这些概念是不容易的。入门可能不想让您感到困惑,并避免引入诸如">左值"、">右值"one_answers">xvalue"之类的术语。不幸的是,这些是理解decltype
如何工作的基础。
首先,求值表达式的类型既不是引用类型,也不是非类类型(例如int const
或int&
)的顶级const
限定类型。如果表达式的类型为int&
或int const
,则在进一步求值之前立即将其转换为int
。
这在c++ 11标准的第5/5和5/6段中有规定:
5如果一个表达式最初的类型是" reference to T "(8.3.2, 8.5.3),则该类型先调整为
T
有进一步的分析吗?表达式指定由引用表示的对象或函数,而表达式是左值或xvalue,具体取决于表达式。6如果prvalue的初始类型为" cv T ",其中
T
是cv不限定的非类、非数组类型,则类型为在进一步分析之前,表达式被调整为T
。
表达式到此为止。decltype
做什么?对于给定表达式e
,确定decltype(e)
结果的规则在第7.1.6.2/4段中指定:
decltype(e)
表示的类型定义如下:—如果
e
是一个未加括号的id-expression或一个未加括号的类成员access (5.2.5),decltype(e)
. 0为e
命名的实体类型。如果没有这样的实体,或者e
命名了一组重载函数,程序格式错误;—否则,如果
e
为xvalue,则decltype(e)
为T&&
,其中T
为e
的类型;—否则,如果
e
为左值,则decltype(e)
为T&
,其中T
为e
的类型;—否则,
decltype(e)
为e
的类型。
decltype
说明符的操作数是一个未求值的操作数(第5条)。
这听起来确实令人困惑。让我们试着一步一步地分析。首先:
-如果
e
是未加括号的id-expression或未加括号的类成员访问(5.2.5),则decltype(e)
. s-为
e
命名的实体类型。如果没有这样的实体,或者e
命名了一组重载函数,程序格式错误;这很简单。如果
e
只是变量的名称,并且没有将其放在括号内,那么decltype
的结果就是该变量的类型。所以bool b; // decltype(b) = bool int x; // decltype(x) = int int& y = x; // decltype(y) = int& int const& z = y; // decltype(z) = int const& int const t = 42; // decltype(t) = int const
注意,这里
decltype(e)
的结果不一定与求值表达式e
的类型相同。例如,表达式z
的求值产生int const
类型的值,而不是int const&
类型的值(因为在第5/5段中,&
被剥离了,正如我们前面看到的)。让我们看看当表达式不只是一个标识符时会发生什么:
—否则,如果
e
为xvalue,则decltype(e)
为T&&
,其中T
为e
的类型;事情越来越复杂了。什么是xvalue?基本上,它是表达式可以属于的三个类别之一(xvalue、lvalue或prvalue)。xvalue通常在调用返回类型为右值引用类型的函数时获得,或者作为静态强制转换为右值引用类型的结果。典型的例子是调用
std::move()
。使用标准中的措辞:
例如,表达式[注意:如果表达式为
,则为xvalue——隐式或显式调用返回类型为右值引用的函数的结果对象类型
-转换为对象类型的右值引用,
——类成员访问表达式,指定非引用类型的非静态数据成员,其中对象表达式为xvalue或
—
一般来说,该规则的效果是命名右值引用被视为左值,未命名右值对对象的引用被视为xvalues;右值对函数的引用被视为左值不管有没有名字。-end note].*
的成员指针表达式,其中第一个操作数是xvalue,第二个操作数是指向数据成员的指针。std::move(x)
、static_cast<int&&>(x)
和std::move(p).first
(对于类型为pair
的对象p
)是xvalue。当您将decltype
应用于xvalue表达式时,decltype
将&&
附加到表达式的类型后面:int x; // decltype(std::move(x)) = int&& // decltype(static_cast<int&&>(x)) = int&&
让我们继续:
左值是什么?非正式地说,lvalue—否则,如果
e
为左值,则decltype(e)
为T&
,其中T
为e
的类型;表达式是表示可以在程序中重复引用的对象的表达式,例如具有名称的变量和/或可以取其地址的对象。对于类型为
T
的左值表达式e
,decltype(e)
的结果为T&
。例如:int x; // decltype(x) = int (as we have seen) // decltype((x)) = int& - here the expression is parenthesized, so the // first bullet does not apply and decltype appends & to the type of // the expression (x), which is int
对于返回类型为
T&
的函数的函数调用也是左值表达式,因此:最后int& foo() { return x; } // decltype(foo()) = int&
:
—否则,
decltype(e)
为e
的类型。如果表达式既不是xvalue也不是lvalue(换句话说,如果它是prvalue),则
decltype(e)
的结果就是e
的类型。未命名的临时值和字面量是右值。例如:int foo() { return x; } // Function calls for functions that do not return // a reference type are prvalue expressions // decltype(foo()) = int // decltype(42) = int
让我们把上面的应用到你的问题中的例子。给定这些声明:
int i = 3, *ptr = &i, &ref = i; decltype(ref + 0) j; decltype(*ptr) k; decltype(a = b) l;
j
的类型将是int
,因为operator +
返回类型为int
的prvalue。k
的类型将是int&
,因为一元operator *
产生一个左值(见第5.3.1/1段)。l
的类型也是int&
,因为operator =
的结果是左值(见第5.17/1段)。关于你问题的这一部分:
你可能误解了书中的那一段。并非所有但是根据第二条规则,由于表达式产生的对象类型可以位于赋值操作的左侧(在本例中为int),那么decltype不应该产生ref to int(int&)类型吗?
int
类型的对象都可以位于赋值操作的左侧。例如,下面的赋值是非法的:int foo() { return 42; } foo() = 24; // ERROR! foo() is a prvalue expression, cannot be on the left // side of an assignment
表达式是否可以出现在赋值操作的左侧(注意,这里我们讨论的是基本数据类型的内置赋值操作符)取决于该表达式的值类别(左值,x值或prvalue),表达式的值类别与其类型无关。
对于表达式,如示例中所示,如果参数为左值,decltype将提供一个引用类型。
7.1.6.2p4:
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).
[ Example:
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = i; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4 = x3; // type is const double&
—end example ]