不正式理解这些概念并不容易。入门可能不想让您感到困惑,并避免引入诸如“lvalue”、“rvalue”和“xvalue”之类的术语。不幸的是,这些对于理解 decltype 的工作原理是很重要的。
首先,求值表达式的类型绝不是引用类型,也不是非类类型的顶级const 限定类型(例如int const 或int&)。如果表达式的类型是int& 或int const,它会在任何进一步评估之前立即转换为int。
这是在 C++11 标准的第 5/5 和 5/6 段中指定的:
5 如果表达式最初的类型为“对 T 的引用”(8.3.2、8.5.3),则在
任何进一步的分析。表达式指定引用表示的对象或函数,而
表达式是 lvalue 或 xvalue,具体取决于表达式。
6 如果 prvalue 最初的类型为“cv T”,其中T 是 cv-unqualified 非类、非数组类型,则
在进行任何进一步分析之前,将表达式调整为 T。
表达方式就这么多。 decltype 是做什么的?好吧,为给定表达式e 确定decltype(e) 结果的规则在第7.1.6.2/4 段中指定:
decltype(e)表示的类型定义如下:
——如果e是一个无括号的id-expression或一个无括号的类成员访问(5.2.5),decltype(e)
是由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)
是由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 通常在调用返回类型为 rvalue 引用类型的函数时获得,或者作为静态转换为 rvalue 的结果em> 引用类型。典型的例子是调用std::move()。
使用标准中的措辞:
[ 注意:一个表达式是一个 xvalue 如果它是:
— 调用函数的结果,无论是隐式还是显式,其返回类型是 rvalue 引用
对象类型,
— 对对象类型的 rvalue 引用的强制转换,
——一个类成员访问表达式,指定一个非引用类型的非静态数据成员,其中
对象表达式是一个xvalue,或者
——一个.*指向成员的表达式,其中第一个操作数是xvalue,第二个操作数是
指向数据成员的指针。
一般来说,这条规则的效果是命名的rvalue引用被视为lvalues和未命名的rvalue
对对象的引用被视为xvalues; rvalue 对函数的引用被视为 lvalues
命名与否。 ——尾注]
例如,表达式std::move(x)、static_cast<int&&>(x) 和std::move(p).first(对于p 类型的pair 对象)是xvalues。当您将 decltype 应用于 xvalue 表达式时,decltype 会将 && 附加到表达式的类型:
int x; // decltype(std::move(x)) = int&&
// decltype(static_cast<int&&>(x)) = int&&
让我们继续:
——否则,如果e是一个左值,decltype(e)是T&,其中T是e的类型;
什么是左值?好吧,非正式地,lvalue 表达式是表示可以在程序中重复引用的对象的表达式 - 例如具有名称的变量和/或您可以获取地址的对象。
对于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),则@的结果987654395@ 只是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 类型的prvalue。 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
表达式是否可以出现在赋值的左侧(注意,我们在这里讨论的是基本数据类型的内置赋值运算符)取决于该表达式的值类别(lvalue、xvalue或prvalue),并且表达式的值类别独立于其输入。