不正式理解这些概念并不容易。入门可能不想让您感到困惑,并避免引入诸如“左值”、“右值”和“ xvalue ”之类的术语。不幸的是,这些对于理解其decltype
工作原理是基本的。
const
首先,评估表达式的类型永远不是引用类型,也不是非类类型(例如int const
or 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
命名了一组重载函数,则程序是非良构的;
— 否则,如果e
是xvalue,decltype(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 段中&
,正如我们之前看到的那样,它被剥离了)。
让我们看看当表达式不仅仅是一个标识符时会发生什么:
— 否则,如果e
是xvalue,decltype(e)
是T&&
,其中T
的类型是e
;
这变得越来越复杂。什么是xvalue?基本上,它是表达式可以属于的三个类别之一(xvalue、lvalue或prvalue)。xvalue通常在调用返回类型为右值引用类型的函数时获得,或者作为静态转换为右值引用类型的结果。典型的例子是调用std::move()
.
要使用标准中的措辞:
[ 注意:表达式是一个xvalue,如果它是:
— 调用函数的结果,无论是隐式还是显式,其返回类型是对对象类型的右值引用,
—对对象类型的右值引用的强制转换,
— 类成员访问表达式,指定非引用类型的非静态数据成员,其中对象表达式是xvalue,或
—.*
指向成员的表达式,其中第一个操作数是xvalue,第二个操作数是指向数据成员的指针。
一般来说,这条规则的效果是命名的右值引用被视为左值,而对对象的未命名的右值
引用被视为xvalues;对函数的右值引用被视为左值,无论是否命名。——尾注]
因此,例如,表达式std::move(x)
、static_cast<int&&>(x)
和(对于类型std::move(p).first
为 的对象)是 xvalues。应用于xvalue表达式时,附加到表达式的类型:p
pair
decltype
decltype
&&
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
表达式是否可以出现在赋值的左侧(注意,我们在这里讨论的是基本数据类型的内置赋值运算符)取决于该表达式的值类别(lvalue、xvalue或prvalue) ,并且表达式的值类别独立于其类型。