当decltype应用于它们时,哪些expression式产生引用types?
我正在阅读C ++ Primer,当expression式产生一个对象types,并产生一个对象的引用types时,我不太明白。
我引用这本书:
- 当我们将decltype应用于不是variables的expression式时,我们得到expression式产生的types。
- 一般来说,decltype会返回一个expression式的引用types,这些expression式会产生可以位于赋值左侧的对象。
考虑下面的代码:
int i = 3, *ptr = &i, &ref = i; decltype(ref + 0) j;
在上面的代码中,expression式“ref + 0”产生了ref引用的对象的值i和0的附加值的固有操作。因此,按照第一个规则,expression式产生一个inttypes。 但是按照第二条规则,因为expression式产生了一个可以站在赋值左边的对象的types(在本例中为int),decltype不应该产生一个int(int&)types的引用吗?
这本书还说,下面的代码
decltype(*ptr) k;
k的types是int&而不是int,expression式的结果types。
它还表示,像下面的代码中的分配expression式
decltype(a = b) l;
l将在赋值操作的左侧具有对象的引用types。
我们如何知道哪些expression式产生对象types,哪些产生对对象types的引用?
没有正式的理解这些概念并不容易。 引子可能不想混淆你,避免引入诸如“ 左值 ”,“ 右值 ”和“ x 值 ”之类的术语。 不幸的是,这些是理解decltype
如何工作的基础。
首先,评估expression式的types永远不是引用types,也不是非类types的顶级const
限定types(例如int const
或int&
)。 如果expression式的types是int&
或int const
,那么在进行任何进一步的评估之前,它会立即转换为int
。
这在C ++ 11标准的第5/5和5/6节中有详细说明:
5如果expression式最初具有“对T的引用”(8.3.2,8.5.3)types,则在进行任何进一步分析之前将types调整为
T
expression式指定由引用表示的对象或函数,expression式是左值或x 值 ,具体取决于expression式。6如果一个prvalue最初的types是“cv T”,其中
T
是一个cv不合格的非类非数组types,那么在进一步分析之前,expression式的types被调整为T
这么多表情。 decltype
做什么? 那么,确定给定expression式e
的decltype(e)
结果的规则在第7.1.6.2/4节中说明:
decltype(e)
表示的types定义如下:– 如果
e
是未经授权的idexpression式或未经授权的类成员访问(5.2.5),则decltype(e)
是由e
命名的实体的types。 如果没有这样的实体,或者如果e
名称是一组超载的函数,则该程序是不合格的;– 否则,如果
e
是一个x值 ,则decltype(e)
是T&&
,其中T
是e
的types;否则,如果
e
是左值 ,则decltype(e)
是T&
,其中T
是e
的types;否则,
decltype(e)
是decltype(e)
的types。
decltype
说明符的操作数是一个未评估的操作数(第5章)。
这确实听起来令人困惑。 让我们试着分析一下。 首先:
– 如果
e
是未经授权的idexpression式或未经授权的类成员访问(5.2.5),则decltype(e)
是由e
命名的实体的types。 如果没有这样的实体,或者如果e
名称是一组重载的函数,则该程序是不合格的;
这很简单。 如果e
只是variables的名称,而不是放在括号内,那么decltype
的结果就是该variables的types。 所以
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)
结果不一定与评估expression式e
的types相同。 例如,对expression式z
的求z
产生了一个int const
types的值,而不是int const&
(因为我们之前已经看到, &
5/5中的&
gets被剥离了)。
让我们看看当expression式不仅仅是一个标识符时会发生什么:
– 否则,如果
e
是一个x值 ,则decltype(e)
是T&&
,其中T
是e
的types;
这变得越来越复杂。 什么是xvalue ? 基本上,它是expression可以属于的三个类别之一( xvalue , lvalue或prvalue )。 一个xvalue通常在调用具有右值引用types的返回types的函数时获得,或者作为静态转换为右值引用types的结果时获得。 典型的例子是调用std::move()
。
要使用标准的措词:
[注:expression式是一个xvalue,如果是:
– 调用函数的结果,无论是隐式的还是显式的,其返回types是对象types的右值引用,
– 转换为对象types的右值引用,
– 一个类成员访问expression式,指定一个非引用types的非静态数据成员,其中的对象expression式是一个xvalue ,或者
– a
.*
指向成员的expression式,其中第一个操作数是一个xvalue ,第二个操作数是指向数据成员的指针。一般来说,这个规则的作用是将名为右 值的引用视为左 值,而对对象的右值引用则视为x值 ; 对函数的右值引用被视为左值,无论是否被命名。 – 注意]
因此,例如,expression式std::move(x)
, static_cast<int&&>(x)
和std::move(p).first
(对于typespair
的对象p
)是xvalues。 当您将decltype
应用于xvalueexpression式时, decltype
将&&
附加到expression式的types:
int x; // decltype(std::move(x)) = int&& // decltype(static_cast<int&&>(x)) = int&&
让我们继续:
否则,如果
e
是左值 ,则decltype(e)
是T&
,其中T
是e
的types;
什么是左值 ? 那么,非正式的左值expression式就是expression式,它表示可以在程序中被重复引用的对象 – 例如带有名称的variables和/或可以取地址的对象。
对于左值expression式T
的expression式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
函数调用返回types为T&
函数也是一个左值expression式,所以:
int& foo() { return x; } // decltype(foo()) = int&
最后:
否则,
decltype(e)
是decltype(e)
的types。
如果expression式不是一个xvalue也不是一个左值 (换句话说,如果它是一个prvalue ), decltype(e)
的结果就是decltype(e)
的types。 未命名的临时文字和文字是prvalues 。 举个例子:
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
的types将是int
,因为operator +
返回一个int
types的prvalue 。 k
的types将是int&
,因为一元运算operator *
产生一个左值 (参见段落5.3.1 / 1)。 l
的types也是int&
,因为operator =
的结果是一个左值 (见第5.17 / 1段)。
关于你的问题的这个部分:
但是按照第二条规则,因为expression式产生了一个可以站在赋值左边的对象的types(在本例中为int),decltype不应该产生一个int(int&)types的引用吗?
你可能误解了书中的这段话。 并非所有 int
types的对象都可以在赋值的左侧。 例如,下面的任务是非法的:
int foo() { return 42; } foo() = 24; // ERROR! foo() is a prvalue expression, cannot be on the left // side of an assignment
一个expression式是否可以出现在赋值的左边(注意,我们在这里讨论基本数据types的内置赋值运算符)取决于该expression式的值类别 ( 左值 , 右 值还是右值 ) ,expression式的值类别与其types无关。
对于expression式,就像在你的例子中一样,如果参数是左值,decltype将提供一个引用types。
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 ]