关于将const引用绑定到临时的子对象

时间:2016-03-11 18:23:50

标签: c++ reference language-lawyer temporary object-lifetime

使用类似

的代码
#include <iostream>

struct P {
    int x;
    P(int x) : x(x) {}
    ~P() { std::cout << "~P()\n"; }
};

int main() {
    auto const& x = P{10}.x;
    std::cout << "extract\n";
}

GCC打印~P() extract,表示参考不会延长临时生命周期。

相比之下,Clang(IMO正确)将临时的生命周期延长到引用x的生命周期,因此析构函数将在{{1}之后的输出后调用 }。

请注意,如果我们而不是main使用某种类型(例如int),GCC会突然显示Clang的行为。

这是GCC中的错误还是标准允许的内容?

3 个答案:

答案 0 :(得分:12)

这由CWG 1651

涵盖
  

问题6161213的解决方案,是会员的结果   应用于prvalue和xvalue的访问或下标表达式,意思是   绑定对临时的这种子对象的引用不会   延长临时寿命。 12.2 [class.temporary]应该是   修改以确保它确实如此。

现状是only prvalues are treated as referring to temporaries - 因此[class.temporary]/5“第二个上下文是指引用绑定到临时。”)不适用。但是,Clang和GCC实际上没有实施问题616的决议。 center().x is treated as a prvalue by both。我最好的猜测:

  • GCC根本没有对任何DR做出反应。使用标量子对象时,它不会延长使用寿命,因为[dcl.init.ref]/(5.2.1.1) 涵盖 。因此,完整的临时对象不需要依赖(参见aschelper's answer),但它不会,因为引用不直接绑定。如果子对象是类或数组类型,则引用直接绑定,GCC延长临时生命周期。这已在DR 60297中注明。

  • Clang已经识别成员访问并实施了“新的”生命周期扩展规则 - 甚至handles casts。从技术上讲,这与处理价值类别的方式不一致。但是,一旦上述DR得到解决,它就更明智,也是正确的行为。

因此,我认为海湾合作委员会目前的措辞是正确的,但目前的措辞有缺陷和含糊不清,而且Clang已经实施了待审议的DR 1651决议,即N3918。本文非常清楚地介绍了这个例子:

  

如果E1是临时表达式且E2未指定   位字段,然后E1.E2是临时表达式。

根据论文对[expr.call] / 11的措辞,

center()是一个临时表达式。因此,上述[class.temporary] / 5中修改后的措辞适用:

  

第二个上下文是指引用不直接绑定(8.5.3   dcl.init.ref)或使用临时表达式初始化(第5条)。 相应的临时   对象(如果有)在引用的生命周期内持续存在,但: [...不适用的例外...]

Voilà,我们有终身延期。注意“相应的临时对象”不够清楚,这是提案推迟的原因之一;一旦修改,它肯定会被采纳。

  

是xvalue(但不是位字段),类prvalue,数组prvalue 或函数lvalue,“cv1 T1”与“cv2 T2”引用兼容,或[...]

实际上,GCC完全尊重这一点,并且如果子对象具有数组类型,则会延长生命周期。

答案 1 :(得分:9)

我会争论g ++中的错误,因为引用draft N3242,§12.2/ 5:

  

第二个上下文是引用绑定到临时的。引用所在的临时值   绑定或作为绑定引用的子对象的完整对象的临时在引用的生命周期内持续存在除外:

所以必须延长其寿命,除非:

  

临时绑定到构造函数的ctor-initializer [..]

中的引用成员      

临时绑定到函数调用[..]

中的引用参数      

函数返回语句[..]

中临时绑定到返回值的生命周期      

临时绑定到new-initializer [..]

中的引用

我们的案例不适合任何这些例外,因此必须遵守规则。我会说g ++在这里错了。

然后,关于从同一草案§8.5.3/ 5(强调我的)中提出的引用aschepler:

  

对类型“ cv1 T1”的引用由“ cv2 T2”类型的表达式初始化,如下所示:

     
      
  1. 如果引用是左值引用和初始化表达式

         

    一个。是左值(但不是位字段),“ cv1 T1”与“ cv2 T2”引用兼容,或者

         

    湾有班级类型......

         

    然后......

  2.   
  3. 否则,引用应该是对非易失性const类型的左值引用(即 cv1 应为const),或者引用应为右值引用

         

    一个。如果初始化表达式

         
        
    • 我。 是xvalue ,类prvalue,数组prvalue或函数lvalue,“ cv1 T1”与“ cv2 {引用兼容” {1}}“,或

    •   
    • II。有班级类型......

    •   
         

    然后引用绑定到第一种情况下初始化表达式的值 ....

         

    湾否则,将使用非引用复制初始化(8.5)的规则从初始化表达式创建并初始化类型为“ cv1 T2”的临时类型。然后将引用绑定到临时。

  4.   

查看xvalue是什么,这次引用http://en.cppreference.com/w/cpp/language/value_category ...

  

xvalue(“到期值”)表达式为[..]

     

T1,对象表达式的成员,其中a是rvalue,m是非引用类型的非静态数据成员;

...表达式a.m 应该 xvalue,因此§8.5.3/ 5中的情况2a适用(并且副本)。我会坚持我的建议:g ++是错误的。

答案 2 :(得分:5)

请阅读Columbo's answer

这是一个gcc错误。相关规则位于[class.temporary]

  

有两种情况下,临时表在与完整表达结束时不同的点被销毁。 [...]

     

第二个上下文是引用绑定到临时的。绑定引用的临时对象或绑定引用的子对象的完整对象的临时值仍然存在   在参考文件的生命周期除外:
   - 绑定到函数调用(5.2.2)中的引用参数的临时对象将持续到完成   包含呼叫的完整表达式    - 函数返回语句(6.6.3)中返回值的临时绑定的生存期不是   扩展;临时在return语句中的完整表达结束时被销毁    - 临时绑定到 new-initializer (5.3.4)中的引用仍然存在,直到完成   包含 new-initializer 的完整表达式。

我们正在绑定对临时子对象的引用,因此临时应该在引用的生命周期内持续存在。这条规则的三个例外都不适用于此。