c++ - 左值右值 - typeid




当decltype应用于它们时,哪些表达式产生引用类型? (2)

如果不正式就理解这些概念并不容易。 引子可能不想让你迷惑并避免引入诸如“ 左值 ”,“ 右值 ”和“ x ”之类的术语。 不幸的是,这些是了解decltype如何工作的基础。

首先,求值表达式的类型永远不是引用类型,也不是非类类型的顶级const -qualified类型(例如int constint& )。 如果表达式的类型变为int&int const ,则在进一步评估之前立即将其转换为int

这在C ++ 11标准的第5/5和5/6段中有详细说明:

5如果表达式最初具有“对T的引用”类型(8.3.2,8.5.3),则在进行任何进一步分析之前将类型调整为T 表达式指定由引用表示的对象或函数,表达式是左值或x ,具体取决于表达式。

6如果prvalue最初具有类型“cv T”,其中T是cv非限定的非类非数组类型,则在进行任何进一步分析之前将表达式的类型调整为T

表达方式如此之多。 decltype做什么? 那么,确定给定表达式edecltype(e)结果的规则在7.1.6.2/4中规定:

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

- 如果e是未加密码的id-expression或不加括号的类成员访问(5.2.5),则decltype(e)e命名的实体的类型。 如果没有这样的实体,或者如果e命名一组重载函数,则该程序是不正确的;

- 否则,如果e是x值,则decltype(e)T&& ,其中Te的类型;

- 否则,如果e左值 ,则decltype(e)T& ,其中Te的类型;

- 否则, decltype(e)decltype(e)的类型。

decltype说明符的操作数是未评估的操作数(第5条)。

这听起来确实令人困惑。 让我们尝试逐个分析它。 首先:

- 如果e是未加密码的id-expression或不加括号的类成员访问(5.2.5),则decltype(e)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的求z产生int const类型的值,而不是int const& (因为在第5/5段中, &被剥离,正如我们之前所见)。

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

- 否则,如果e是x值,则decltype(e)T&& ,其中Te的类型;

这变得复杂了。 什么是xvalue ? 基本上,它是表达式可以属于的三个类别之一( xvaluelvalueprvalue )。 xvalue通常在调用具有rvalue引用类型的返回类型的函数时获得,或者作为静态强制转换为rvalue引用类型的结果获得。 典型的例子是对std::move()的调用。

要使用标准中的措辞:

[注意:表达式是xvalue,如果它是:

- 调用函数的结果,无论是隐式还是显式,其返回类型是对象类型的右值引用,

- 对对象类型的右值引用的强制转换,

- 一个类成员访问表达式,指定非引用类型的非静态数据成员,其中对象表达式是xvalue ,或者

- a .*指向成员的表达式,其中第一个操作数是xvalue ,第二个操作数是指向数据成员的指针。

通常,此规则的作用是将命名的 引用视为 值,将对象的未命名右值引用视为xvalues ; 对函数的右值引用被视为左值,无论是否命名。 - 尾注]

因此,例如,表达式std::move(x)static_cast<int&&>(x)std::move(p).first (对于类型pair的对象p )是xvalues。 将decltype应用于xvalue表达式时, decltype会将&&附加到表达式的类型:

int x; // decltype(std::move(x)) = int&&
       // decltype(static_cast<int&&>(x)) = int&&

让我们继续:

- 否则,如果e左值 ,则decltype(e)T& ,其中Te的类型;

什么是左值 ? 嗯,非正式地, 左值表达式是表示可以在程序中重复引用的对象的表达式 - 例如,带有名称的变量和/或可以取地址的对象。

对于作为左值表达式的类型T的表达式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

对返回类型为T&的函数的函数调用也是左值表达式,因此:

int& foo() { return x; } //  decltype(foo()) = int& 

最后:

- 否则, decltype(e)decltype(e)的类型。

如果表达式不是xvaluelvalue (换句话说,如果它是prvalue ),则decltype(e)的结果就是decltype(e)的类型。 未命名的临时文字和文字是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的类型将是int ,因为operator +返回int类型的prvaluek的类型将是int& ,因为一元运算operator *产生左值 (见第5.3.1 / 1段)。 l的类型也是int& ,因为operator =的结果是左值 (见第5.17 / 1段)。

关于这部分问题:

但是按照第二条规则,因为表达式产生的对象类型可以位于赋值的左侧(在本例中为int),decltype不应该产生对int(int&)类型的引用吗?

你可能误解了书中的那段经文。 并非所有 int类型的对象都可以位于赋值的左侧。 例如,以下分配是非法的:

int foo() { return 42; }

foo() = 24; // ERROR! foo() is a prvalue expression, cannot be on the left
            // side of an assignment

表达式是否可以出现在赋值的左侧(注意,我们在这里讨论基本数据类型的内置赋值运算符)取决于该表达式的值类别lvaluexvalueprvalue ) ,表达式的值类别与其类型无关。

我正在阅读C ++ Primer,并且在表达式产生对象类型时,以及何时为对象生成引用类型时,我无法理解。

我从这本书中引用:

  1. 当我们将decltype应用于不是变量的表达式时,我们得到>表达式产生的类型。
  2. 一般来说,decltype为表达式返回一个引用类型,这些表达式产生的对象可以位于赋值的左侧。

考虑以下代码:

int i = 3, *ptr = &i, &ref = i;
decltype(ref + 0) j;

在上面的代码中,表达式“ref + 0”导致添加ref引用的对象的值的固有操作,i和0.因此,通过第一个规则,表达式产生int类型。 但是按照第二条规则,因为表达式产生的对象类型可以位于赋值的左侧(在本例中为int),decltype不应该产生对int(int&)类型的引用吗?

该书还说,对于以下代码

decltype(*ptr) k;

k的类型为int&而不是int,表达式导致的类型。

它还表示对于赋值表达式,如下面的代码

decltype(a = b) l;

l将在赋值操作的左侧具有对象的引用类型。

我们如何知道哪些表达式产生对象类型以及哪些表达式产生对象类型的引用?


对于表达式,如示例所示,如果参数为lvalue,则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 ]




decltype