c++ - Acerca de vincular una referencia constante a un subobjeto de un temporal
reference language-lawyer (3)
Acabo de leer la respuesta de Columbo .
Este es un error de gcc. La regla relevante está en [class.temporary]/5 :
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 una 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 toda la vida útil de la referencia, excepto:
- Un objeto temporal vinculado a un parámetro de referencia en una llamada de función (5.2.2) persiste hasta la finalización de la expresión completa que contiene la llamada.
- La vida útil de un enlace temporal al valor devuelto en una declaración de retorno de función (6.6.3) no se extiende; lo temporal se destruye al final de la expresión completa en la declaración 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 toda la vida útil de la referencia. Ninguna de esas tres excepciones a esta regla se aplica aquí.
Con código como
#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 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 de la temporal a la vida útil de la referencia
x
y, por lo tanto, el destructor se llamará
después de
la salida en
main
.
Tenga en cuenta que GCC de repente muestra el comportamiento de Clang si, en lugar de
int
, usamos algún tipo de clase (por ejemplo,
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 un miembro de acceso o expresión de subíndice aplicado a un prvalue sea un xvalue, significa que vincular una referencia a un subobjeto de tal temporal no extiende la vida útil del temporal. 12.2 [class.temporary] debe revisarse para asegurarse de que así sea.
El statu quo es que
solo los valores se tratan como referencias temporales
, por lo tanto
[class.temporary]/5
(
"El segundo contexto es cuando una referencia está vinculada a un temporal"
) no se considera aplicable.
Sin embargo, Clang y GCC no han implementado realmente la resolución del problema 616.
center().x
es tratado como un valor por ambos
.
Mi mejor suposición:
-
GCC simplemente no reaccionó a ningún DR todavía, en absoluto. No extiende la vida útil cuando se usan subobjetos escalares , porque esos no están cubiertos por [dcl.init.ref]/(5.2.1.1) † . Por lo tanto, el objeto temporal completo no necesita vivir (ver la respuesta de un corredor ), y no lo hace, porque la referencia no se une directamente. Si el subobjeto es de clase o tipo de matriz, la referencia se une directamente y GCC extiende la vida útil del temporal. Esto se ha observado en DR 60297 .
-
Clang reconoce el acceso de los miembros y ya implementó las "nuevas" reglas de extensión de por vida, incluso maneja los lanzamientos . Técnicamente hablando, esto no es consistente con la forma en que maneja las categorías de valores. Sin embargo, es más sensible y será el comportamiento correcto una vez que se resuelva el DR mencionado anteriormente.
Por lo tanto, diría que GCC es correcto según la redacción actual, pero la redacción actual es defectuosa y vaga, y Clang ya implementó la resolución pendiente para DR 1651, que es N3918 . Este artículo cubre el ejemplo muy claramente:
Si
E1
es una expresión temporal yE2
no designa un campo de bits, entoncesE1.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 mencionado anteriormente:
El segundo contexto es cuando una referencia no se une 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 la vida útil 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 del aplazamiento de la propuesta; seguramente será adoptado una vez que sea revisado.
†
es un xvalue (pero no un campo de bits), class prvalue, array prvalue o function lvalue y "
cv1 T1
" es compatible con "cv2 T2
", o […]
De hecho, GCC respeta esto completamente y extenderá la vida útil si el subobject tiene un tipo de matriz.
Yo argumentaría por un error en g ++, porque, citando el borrador N3242 , §12.2 / 5:
El segundo contexto es cuando una referencia está vinculada a una 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 toda la vida útil de la referencia, excepto:
Por lo tanto, su vida útil debe extenderse, excepto cuando:
Un enlace temporal a un miembro de referencia en un ctor-initializer del constructor [..]
Un enlace temporal a un parámetro de referencia en una llamada de función [..]
La duración de un enlace temporal al valor devuelto en una declaración de retorno de función [..]
Un enlace 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 de un separador del mismo borrador §8.5.3 / 5 (énfasis mío):
Una referencia al tipo " cv1
T1
" se inicializa mediante una expresión del tipo " cv2T2
" de la siguiente manera:
Si la referencia es una referencia de valor y la expresión inicializadora
a. es un valor l (pero no es un campo de bits) y " cv1
T1
" es compatible con la referencia con " cv2T2
", osegundo. tiene un tipo de clase ...
entonces ...
De lo contrario, la referencia será una referencia de valor a un tipo de constante no volátil (es decir, cv1 será
const
), o la referencia será una referencia de valor.a. Si la expresión inicializadora
yo. es un xvalue , class prvalue, array prvalue o function lvalue y " cv1
T1
" es compatible con " cv2T2
", oii) tiene un tipo de clase ...
entonces la referencia está vinculada al valor de la expresión inicializadora en el primer caso ...
segundo. De lo contrario, se crea un temporario de tipo " cv1
T1
" y se inicializa a partir de la expresión inicializadora utilizando las reglas para una copia-inicialización sin referencia (8.5). La referencia está vinculada a lo temporal.
Mirando qué es un xvalue, esta vez citando http://en.cppreference.com/w/cpp/language/value_category ...
Una expresión xvalue ("valor de expiración") es [..]
am
, el miembro de la expresión de objeto, donde a es un rvalue ym es un miembro de datos no estático de tipo no referencial;
... el
center().x
expresión
center().x
debe ser
un valor
center().x
, por lo tanto, se aplica el caso 2a del §8.5.3 / 5 (y
no
la copia).
Me quedaré con mi sugerencia: g ++ está mal.