37

我正在阅读 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 将在赋值操作的左侧具有对对象的引用类型。

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

4

2 回答 2

50

不正式理解这些概念并不容易。入门可能不想让您感到困惑,并避免引入诸如“左值”、“右值”和“ xvalue ”之类的术语。不幸的是,这些对于理解其decltype工作原理是基本的。

const首先,评估表达式的类型永远不是引用类型,也不是非类类型(例如int constor int&)的顶级限定类型。如果表达式的类型是int&or ,它会在任何进一步评估之前int const立即转换为。int

这在 C++11 标准的第 5/5 和 5/6 段中指定:

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

6 如果纯右值最初的类型为“cv T”,其中T是 cv 非限定的非类、非数组类型,则在T进一步分析之前将表达式的类型调整为。

表达方式就这么多。做什么decltype?好吧,确定decltype(e)给定表达式结果的规则e在第 7.1.6.2/4 段中指定:

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

— 如果e是无括号的id 表达式或无括号的类成员访问 (5.2.5),decltype(e) 是由 命名的实体的类型e。如果没有这样的实体,或者如果e命名了一组重载函数,则程序是非良构的;

— 否则,如果exvaluedecltype(e)T&&,其中T的类型是e;

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

— 否则,decltype(e)是 的类型e

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

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

— 如果e是无括号的id 表达式或无括号的类成员访问 (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值会产生一个 type 的值int const,而不是int const&(因为在第 5/5 段中&,正如我们之前看到的那样,它被剥离了)。

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

— 否则,如果exvaluedecltype(e)T&&,其中T的类型是e;

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

要使用标准中的措辞:

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

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

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

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

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

一般来说,这条规则的效果是命名的右值引用被视为值,而对对象的未命名的右值 引用被视为xvalues对函数的右值引用被视为值,无论是否命名。——尾注]

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

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

让我们继续:

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

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

对于左值e表达式类型T的表达式,产生. 例如: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也不是左值(换句话说,如果它是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值。的类型k将是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) ,并且表达式的值类别独立于其类型。

于 2013-06-21T18:46:36.733 回答
3

对于表达式,如在您的示例中,如果参数是左值,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 ]
于 2013-06-21T18:21:55.500 回答