Acerca de enlazar una referencia constante a un subobjeto de un temporal

Con código como

#include  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 imprime ~P() extract , lo que indica que la vida útil del temporal no se extiende por la referencia.

Por el contrario, Clang (IMO correctamente) extiende la vida útil del temporal a la vida útil de la referencia x y, por lo tanto, se llamará al destructor después de la salida en main .

Tenga en cuenta que GCC muestra de repente el comportamiento de Clang si, en lugar de int , usamos algún tipo de clase (por ejemplo, una string ).

¿Es esto un error en GCC o algo permitido por el estándar?

Esto está cubierto por CWG 1651 :

La resolución de los problemas 616 y 1213 , que hace que el resultado de una expresión de acceso o subíndice de miembro se aplique a un prvalue un valor x, significa que vincular una referencia a dicho subobjeto de un temporal no extiende la duración del temporal. 12.2 [class.temporary] debería revisarse para garantizar que así sea.

El status quo es que solo los valores pr se tratan como referencias a los temporales , por lo que [class.temporary] / 5 ( “El segundo contexto es cuando una referencia está vinculada a un temporal” ) no se considera aplicable. Sin embargo, Clang y GCC aún no han implementado la resolución del problema 616. center().x es tratado como un prvalue por ambos . Mi mejor suposición:

  • GCC simplemente no reaccionó a ningún DR aún, en absoluto. No extiende el tiempo de vida cuando se utilizan subobjetos escalares , porque esos no están cubiertos por [dcl.init.ref] / (5.2.1.1) . Por lo tanto, no es necesario que el objeto temporal completo siga vivo (consulte la respuesta de aschelper ), y no es así porque la referencia no se vincula directamente. Si el subobjeto es de clase o tipo de matriz, la referencia se vincula directamente, y GCC extiende la vida del temporal. Esto se ha observado en DR 60297 .

  • Clang reconoce el acceso de los miembros e implementó las “nuevas” reglas de extensión de por vida ya que incluso maneja los moldes . Técnicamente hablando, esto no es consistente con la forma en que maneja las categorías de valores. Sin embargo, es más sensato y será el comportamiento correcto una vez que se resuelva el DR antes mencionado.

Por lo tanto, diría que GCC es correcto según la redacción actual, pero la redacción actual es defectuosa e imprecisa, y Clang ya implementó la resolución pendiente en DR 1651, que es N3918 . Este documento cubre el ejemplo muy claramente:

Si E1 es una expresión temporal y E2 no designa un campo de bit, entonces E1.E2 es una expresión temporal.

center() es una expresión temporal según la redacción del artículo para [expr.call] / 11. Por lo tanto, se aplica su redacción modificada en el [class.temporary] / 5 antes mencionado:

El segundo contexto es cuando una referencia no se vincula directamente (8.5.3 dcl.init.ref) o se inicializa con una expresión temporal (cláusula 5). El objeto temporal correspondiente (si lo hay) persiste durante el tiempo de vida de la referencia, excepto: [… excepciones inaplicables …]

Voilà, tenemos extensión de por vida. Tenga en cuenta que “el objeto temporal correspondiente” no es lo suficientemente claro, una de las razones para el aplazamiento de la propuesta; seguramente será adoptado una vez que sea revisado.


es un xvalor (pero no un campo de bits), prvalue de clase , prvalue de matriz o lvalue de función y ” cv1 T1 ” es compatible con referencia con ” cv2 T2 “, o […]

De hecho, GCC respeta esto completamente y extenderá la vida útil si el subobjeto tiene tipo de matriz.

Yo defendería un error en g ++, porque, citando el borrador N3242 , §12.2 / 5:

El segundo contexto es cuando una referencia está vinculada a un temporal. El temporal al que está vinculada la referencia o el temporal que es el objeto completo de un subobjeto al que está vinculada la referencia persiste durante el tiempo de vida de la referencia, excepto:

Por lo tanto, su duración debe extenderse, excepto cuando:

Un límite temporal a un miembro de referencia en el inicializador de ctor de un constructor [..]

Un límite temporal a un parámetro de referencia en una llamada de función [..]

La vida útil de un límite temporal al valor devuelto en una statement de devolución de función [..]

Un límite temporal a una referencia en un new-initializer [..]

Nuestro caso no se ajusta a ninguna de estas excepciones, por lo tanto, debe seguir la regla. Yo diría que g ++ está mal aquí.

Luego, con respecto a la cita aschepler mencionada en el mismo borrador §8.5.3 / 5 (énfasis mío):

Una referencia al tipo ” cv1 T1 ” se inicializa con una expresión del tipo ” cv2 T2 ” de la siguiente manera:

  1. Si la referencia es una referencia lvalue y la expresión inicializador

    a. es un lvalue (pero no es un campo de bits) y ” cv1 T1 ” es compatible con ” cv2 T2 “, o

    segundo. tiene un tipo de clase …

    entonces …

  2. De lo contrario, la referencia será una referencia lvalue a un tipo const non-volátil (es decir, cv1 será const ), o la referencia será una referencia rvalue.

    a. Si la expresión del inicializador

    • yo. es un xvalue , prvalue de clase, prvalue de matriz o lvalue de función y ” cv1 T1 ” es compatible con referencia con ” cv2 T2 “, o

    • ii. tiene un tipo de clase …

    entonces la referencia está vinculada al valor de la expresión del inicializador en el primer caso

    segundo. De lo contrario, se crea un temporal de tipo ” cv1 T1 ” y se inicializa a partir de la expresión del inicializador utilizando las reglas para una inicialización de copia no de referencia (8.5). La referencia está vinculada a lo temporal.

Viendo lo que es un valor x, esta vez citando http://en.cppreference.com/w/cpp/language/value_category

Una expresión xvalue (“valor de caducidad”) es [..]

am , el miembro de expresión de objeto, donde a es un valor r y m es un miembro de datos no estáticos de tipo no referencia;

… la expresión center().x debe ser un xvalor, por lo que se aplica el caso 2a del §8.5.3 / 5 (y no la copia). Me quedaré con mi sugerencia: g ++ está mal.

Solo lea la respuesta de Columbo .


Este es un error de gcc. La regla relevante está en [class.temporary] :

Hay dos contextos en los que los temporales se destruyen en un punto diferente al final de la expresión completa. […]

El segundo contexto es cuando una referencia está vinculada a un temporal. El temporal al que está vinculada la referencia o el temporal que es el objeto completo de un subobjeto al que está vinculada la referencia persiste durante el tiempo de vida de la referencia, excepto:
– Un objeto temporal vinculado a un parámetro de referencia en una llamada a función (5.2.2) persiste hasta la finalización de la expresión completa que contiene la llamada.
– La duración de un límite temporal al valor devuelto en una statement de devolución de función (6.6.3) no se extiende; el temporal se destruye al final de la expresión completa en la statement de devolución.
– Un enlace temporal a una referencia en un nuevo inicializador (5.3.4) persiste hasta la finalización de la expresión completa que contiene el nuevo inicializador .

Estamos vinculando una referencia a un subobjeto de un temporal, por lo que el temporal debe persistir durante la vigencia de la referencia. Ninguna de esas tres excepciones a esta regla se aplica aquí.