【问题标题】:What expressions yield a reference type when decltype is applied to them?当 decltype 应用于它们时,哪些表达式会产生引用类型?
【发布时间】:2013-06-18 23:03:59
【问题描述】:

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

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

【问题讨论】:

    标签: c++ c++11 decltype


    【解决方案1】:

    不正式理解这些概念并不容易。入门可能不想让您感到困惑,并避免引入诸如“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&amp;,其中T是e的类型;

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

    对于T 类型的表达式e,它是一个左值 表达式,decltype(e) 产生T&amp;。比如:

    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&amp;的函数的函数调用也是一个左值表达式,所以:

    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&amp;,因为一元 operator * 产生一个左值(参见第 5.3.1/1 段)。 l 的类型也是int&amp;,因为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),并且表达式的值类别独立于其输入。

    【讨论】:

    • 最后一部分具有误导性 - 它在很大程度上取决于所讨论的类型,当它是 xvalue 或 prvalue 时是否可分配:用户定义的类型可以完美分配,除非只有一个涉及左值引用限定符。
    • @Xeo:嗯,上下文是基本数据类型的上下文,内置的operator =要求左操作数是左值
    • 那么如果现在所有的表达式都是左值、xvalue 或 prvalues,那是否意味着没有更多的右值了? (他们是如何想出新名字的?)
    • @celtschk:它们是右值,但它们不再是基本类别。 rvalues 是 xvalues 和 prvalues 的并集。 xvalue 代表“eXpiring”值,而 prvalue 代表“纯”右值
    • 感谢您的解释。如果两者都是右值,那么他们将“r”保留在“prvalue”中而不是“xvalue”中,这有点奇怪......
    【解决方案2】:

    对于表达式,如在您的示例中,如果参数是左值,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 ]
    

    【讨论】:

    • int i = 0; decltype(i) ii = i; -- ii 这里将不是作为参考,虽然i 是一个左值,所以你的总结不是很准确(注意有还有decltype((i)),这将产生一个参考)。
    猜你喜欢
    • 1970-01-01
    • 2018-12-25
    • 1970-01-01
    • 1970-01-01
    • 2023-01-17
    • 2013-10-14
    • 1970-01-01
    • 1970-01-01
    • 2021-10-30
    相关资源
    最近更新 更多