关于将const引用绑定到临时对象的子对象
与代码一样
#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"; }
海湾合作委员会打印~P() extract
,表明临时的生存期不被引用扩展。
相反,Clang(IMO正确地)将临时的生命周期延长到参考x
的生命周期,因此在main
的输出之后将调用析构函数。
请注意,GCC突然显示了Clang的行为,如果我们,而不是int
,使用一些类types(例如string
)。
这是GCC中的错误还是标准允许的?
这由CWG 1651涵盖:
问题616和1213的解决scheme ,将成员访问或下标expression式的结果应用于一个xvalue,意味着将引用绑定到这样的临时子对象不会延长临时的生命周期。 12.2 [class.temporary]应该修改,以确保它。
现状是, 只有价值被认为是指临时性 – 因此[class.temporary] / 5 ( “第二种情况是当一个参考被绑定到临时” )不被认为是适用的。 但Clang和GCC实际上还没有实现616号决议的解决scheme。 center().x
被两者作为prvalue处理 。 我最好的猜测是:
-
海湾合作委员会根本没有对任何灾难恢复做出反应。 它不会延长使用标 量子 对象的生命周期,因为它们不包含在[dcl.init.ref] /(5.2.1.1)†中 。 所以完整的临时对象不需要活着(参见aschelper的答案 ),而不是,因为引用不直接绑定。 如果子对象是类或数组types,则引用直接绑定,并且GCC扩展临时的生命周期。 这已在DR 60297中注明 。
-
铿锵认识到成员访问,并已经实施了“新的”终身延长规则 – 它甚至处理演员 。 从技术上讲,这与处理价值类别的方式不一致。 但是,一旦解决了上述DR,则更为合理,而且是正确的行为。
因此,我认为海湾合作委员会目前的措辞是正确的,但目前的措辞是有缺陷的,模糊的,铿锵已经执行了1651年的裁决,即N3918 。 本文非常清楚地涵盖了这个例子:
如果
E1
是一个临时expression式而E2
不指定一个位域,那么E1.E2
是一个临时expression式。
center()
是根据[expr.call] / 11的论文措辞的临时expression。 因此,上述[class.temporary] / 5中的修改措词适用于:
第二个上下文是当引用不直接绑定(8.5.3 dcl.init.ref) 或用临时expression式 (子句5) 初始化时 。 相应的临时对象(如果有的话)在引用的生存期内一直存在,除了: […不适用的exception…]
Voilà,我们有一生的延续。 请注意,“相应的临时对象”不够清楚,是提案延期的原因之一; 一旦修改,一定会被采纳。
†
是一个xvalue(但不是位字段), 类prvalue,数组prvalue或函数左值,“
cv1 T1
”与“cv2 T2
”是引用兼容的,
事实上,GCC完全尊重这一点,并且如果子对象具有数组types,将延长生命周期。
我会争论g ++中的一个bug,因为引用N3242草案 §12.2/ 5:
第二个上下文是引用绑定到临时的时候。 引用所绑定的临时对象或引用所绑定的子对象的完整对象的临时对象在引用的生命周期内一直存在,除非:
所以它的寿命必须延长,除非:
临时绑定到构造函数的ctor-initializer中的引用成员[..]
临时绑定到函数调用中的引用参数[..]
临时绑定到返回值的函数返回语句的生命周期[..]
临时绑定到一个
new-initializer
[…]
我们的情况不适合这些例外,因此它必须遵守规则。 我会说g ++在这里是错误的。
然后,关于从同样的草案§8.5.3/ 5(强调矿)提出的报价摘要:
types“ cv1
T1
”的引用由types“ cv2T2
”的expression式初始化,如下所示:
如果引用是一个左值引用和初始化expression式
一个。 是左值(但不是位域),“ cv1
T1
”与“ cv2T2
”参考兼容,或湾 有一个class级types…
然后 …
否则,引用应该是对非易失性常量types的左值引用(即cv1应该是
const
),或者引用应该是右值引用。一个。 如果初始化expression式
一世。 是一个xvalue ,类prvalue,数组prvalue或函数lvalue,“ cv1
T1
”与“ cv2T2
”是引用兼容的,或者II。 有一个class级types…
那么引用在第一种情况下绑定到初始化expression式的值。
湾 否则,使用非引用复制初始化(8.5)的规则从初始化expression式创build并初始化临时types“ cv1
T1
”。 然后参考文件绑定到临时文件。
看看什么是xvalue,这次引用http://en.cppreference.com/w/cpp/language/value_category …
xvalue(“expired value”)expression式是[..]
am
,是对象expression式的成员,其中a是右值,m是非引用types的非静态数据成员;
…expressioncenter().x
.x 应该是一个xvalue,因此来自§8.5.3/ 5的情况2a适用(而不是复制)。 我会留下我的build议:g ++是错误的。
刚刚读过科伦坡的回答 。
这是一个gcc的错误。 相关规则在[class.temporary]中 :
有两种情况下临时销毁在不同于完整expression式结束的点上。 […]
第二个上下文是引用绑定到临时的时候。 引用所绑定的临时对象或引用所绑定的子对象的完整对象的临时对象在引用的生命周期内一直存在,除非:
– 在函数调用中绑定到引用参数的临时对象(5.2.2)将一直存在,直到完成包含调用的完整expression式。
– 函数返回语句(6.6.3)中返回值临时绑定的生命周期不被扩展; 在返回语句中,临时expression式被全expression式结尾处销毁。
– 在新初始化程序 (5.3.4)中临时绑定到引用,直到完成包含新初始化程序的完整expression式。
我们绑定了对临时对象的引用,所以临时对象应该在引用的生命周期中持续存在。 这个规则的三个例外都不适用于此。
编辑:这个答案是不正确的! 正如其他人所指出的, center().x
是一个xvalue,而不是一个右值。
g ++是正确的,这是叮当声中的一个错误。 从草案N3242第8.5.3 / 5节中,将要点改为数字:
types“ cv1
T1
”的引用由types“ cv2T2
”的expression式初始化,如下所示:
如果引用是一个左值引用和初始化expression式
一个。 是左值(但不是位域),“ cv1
T1
”与“ cv2T2
”参考兼容,或湾 有一个class级types…
然后 …
否则,引用应该是对非易失性常量types的左值引用(即cv1应该是
const
),或者引用应该是右值引用。一个。 如果初始化expression式
一世。 是一个xvalue,类prvalue,数组prvalue或函数lvalue,“ cv1
T1
”与“ cv2T2
”是引用兼容的,或者II。 有一个class级types…
那么引用在第一种情况下绑定到初始化expression式的值。
湾 否则,使用非引用复制初始化(8.5)的规则从初始化expression式创build并初始化临时types“ cv1
T1
”。 然后参考文件绑定到临时文件。
由于初始化expression式是一个非类标量的前值,所以点2.ai不适用,最后在情况2.b. 一个临时的double
将通过复制初始化来创build,并且引用绑定到那个,而不是center()
的子对象。
正如你注意到的,如果expression式有一个类的types,事情是不同的,并且引用直接绑定到子对象。