当decltype应用于它们时,哪些expression式产生引用types?

我正在阅读C ++ Primer,当expression式产生一个对象types,并产生一个对象的引用types时,我不太明白。

我引用这本书:

  1. 当我们将decltype应用于不是variables的expression式时,我们得到expression式产生的types。
  2. 一般来说,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 constint& )。 如果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式edecltype(e)结果的规则在第7.1.6.2/4节中说明:

decltype(e)表示的types定义如下:

– 如果e是未经授权的idexpression式或未经授权的类成员访问(5.2.5),则decltype(e)是由e命名的实体的types。 如果没有这样的实体,或者如果e名称是一组超载的函数,则该程序是不合格的;

– 否则,如果e是一个x值 ,则decltype(e)T&& ,其中Te的types;

否则,如果e左值 ,则decltype(e)T& ,其中Te的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 consttypes的值,而不是int const& (因为我们之前已经看到, & 5/5中的& gets被剥离了)。

让我们看看当expression式不仅仅是一个标识符时会发生什么:

– 否则,如果e是一个x值 ,则decltype(e)T&& ,其中Te的types;

这变得越来越复杂。 什么是xvalue ? 基本上,它是expression可以属于的三个类别之一( xvaluelvalueprvalue )。 一个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& ,其中Te的types;

什么是左值 ? 那么,非正式的左值expression式就是expression式,它表示可以在程序中被重复引用的对象 – 例如带有名称的variables和/或可以取地址的对象。

对于左值expression式T的expression式edecltype(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 +返回一个inttypes的prvaluek的types将是int& ,因为一元运算operator *产生一个左值 (参见段落5.3.1 / 1)。 l的types也是int& ,因为operator =的结果是一个左值 (见第5.17 / 1段)。

关于你的问题的这个部分:

但是按照第二条规则,因为expression式产生了一个可以站在赋值左边的对象的types(在本例中为int),decltype不应该产生一个int(int&)types的引用吗?

你可能误解了书中的这段话。 并非所有 inttypes的对象都可以在赋值的左侧。 例如,下面的任务是非法的:

 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 ]