Dans l'exemple simple suivant, pourquoi ref2
Ne peut-il pas être lié au résultat de min(x,y+1)
?
#include <cstdio>
template< typename T > const T& min(const T& a, const T& b){ return a < b ? a : b ; }
int main(){
int x = 10, y = 2;
const int& ref = min(x,y); //OK
const int& ref2 = min(x,y+1); //NOT OK, WHY?
return ref2; // Compiles to return 0
}
exemple en direct - produit:
main:
xor eax, eax
ret
EDIT: L'exemple ci-dessous décrit mieux une situation, je pense.
#include <stdio.h>
template< typename T >
constexpr T const& min( T const& a, T const& b ) { return a < b ? a : b ; }
constexpr int x = 10;
constexpr int y = 2;
constexpr int const& ref = min(x,y); // OK
constexpr int const& ref2 = min(x,y+1); // Compiler Error
int main()
{
return 0;
}
exemple en direct produit:
<source>:14:38: error: '<anonymous>' is not a constant expression
constexpr int const& ref2 = min(x,y+1);
^
Compiler returned: 1
C'est par conception. En un mot, seule la référence nommée à laquelle le temporaire est lié directement prolongera sa durée de vie.
[class.temporary]
5 Il existe trois contextes dans lesquels les temporels sont détruits à un moment différent de la fin de la pleine expression. [...]
6 Le troisième contexte est lorsqu'une référence est liée à un temporaire. Le temporaire auquel la référence est liée ou le temporaire qui est l'objet complet d'un sous-objet auquel la référence est liée persiste pendant la durée de vie de la référence, sauf:
- Un objet temporaire lié à un paramètre de référence dans un appel de fonction persiste jusqu'à la fin de l'expression complète contenant l'appel.
- La durée de vie d'une liaison temporaire à la valeur retournée dans une instruction de retour de fonction n'est pas étendue; le temporaire est détruit à la fin de l'expression complète dans l'instruction return.
- [...]
Vous ne vous êtes pas lié directement à ref2
, et vous le transmettez même via une déclaration de retour. La norme dit explicitement qu'elle ne prolongera pas la durée de vie. En partie pour permettre certaines optimisations. Mais en fin de compte, parce que le suivi de ce qui temporaire devrait être étendu lorsqu'une référence est passée dans et hors des fonctions est insoluble en général.
Étant donné que les compilateurs peuvent optimiser de manière agressive en supposant que votre programme ne présente aucun comportement indéfini, vous voyez une manifestation possible de cela. L'accès à une valeur en dehors de sa durée de vie n'est pas défini, c'est ce que return ref2;
fait , et puisque le comportement n'est pas défini, le simple retour de zéro est un comportement valide à afficher. Aucun contrat n'est rompu par le compilateur.
C'est intentionnel. Une référence ne peut prolonger la durée de vie d'un temporaire que lorsqu'elle est liée à ce temporaire directement. Dans votre code, vous liez ref2
au résultat de min
, qui est une référence. Peu importe que cette référence fasse référence à un temporaire. Seul b
prolonge la durée de vie du temporaire; peu importe que ref2
fait également référence à ce même temporaire.
Une autre façon de voir les choses: vous ne pouvez pas éventuellement avoir d'extension à vie. C'est une propriété statique. Si ref2
ferait la bonne chosetm, puis en fonction des valeurs d'exécution de x
et y+1
la durée de vie est prolongée ou non. Pas quelque chose que le compilateur est capable de faire.
Je vais d'abord répondre à la question, puis fournir un certain contexte pour la réponse. Le projet de travail actuel contient la formulation suivante:
L'objet temporaire auquel la référence est liée ou l'objet temporaire qui est l'objet complet d'un sous-objet auquel la référence est liée persiste pendant toute la durée de vie de la référence si la valeur gl à laquelle la référence est liée a été obtenue par l'un des éléments suivants :
- une conversion de matérialisation temporaire ([conv.rval]),
(
expression)
, où expression est l'une de ces expressions,- indice ([expr.sub]) d'un opérande de tableau, où cet opérande est l'une de ces expressions,
- un accès membre de classe ([expr.ref]) en utilisant le
.
opérateur où l'opérande gauche est l'une de ces expressions et l'opérande droit désigne un membre de données non statique de type non référence,- une opération pointeur sur membre ([expr.mptr.oper]) à l'aide de
.*
opérateur où l'opérande gauche est l'une de ces expressions et l'opérande droit est un pointeur vers un membre de données de type non référence,- une
const_cast
([expr.const.cast]),static_cast
([expr.static.cast]),dynamic_cast
([expr.dynamic.cast]) oureinterpret_cast
([expr.reinterpret.cast]) conversion, sans conversion définie par l'utilisateur, d'un opérande glvalue qui est l'une de ces expressions en une glvalue qui fait référence à l'objet désigné par l'opérande, ou à son objet complet ou un sous-objet celui-ci,- une expression conditionnelle ([expr.cond]) qui est une valeur gl où le deuxième ou le troisième opérande est l'une de ces expressions, ou
- une expression virgule ([expr.comma]) qui est une valeur gl où l'opérande droit est l'une de ces expressions.
Selon cela, lorsqu'une référence est liée à une valeur gl retournée par un appel de fonction, l'extension de durée de vie ne se produit pas, car la valeur gl a été obtenue à partir de l'appel de fonction, qui n'est pas l'une des expressions autorisées pour l'extension de durée de vie.
La durée de vie du y+1
temporaire est étendu une fois lorsqu'il est lié au paramètre de référence b
. Ici, la valeur y+1
est matérialisé pour donner une valeur x, et la référence est liée au résultat de la conversion de matérialisation temporaire; une prolongation de la durée de vie se produit ainsi. Cependant, lorsque la fonction min
revient, ref2
est lié au résultat de l'appel, et l'extension de durée de vie ne se produit pas ici. Par conséquent, la y+1
temporaire est détruit à la fin de la définition de ref2
, et ref2
devient une référence pendante.
Il y a toujours eu une certaine confusion sur ce sujet. Il est bien connu que le code de l'OP et un code similaire aboutissent à une référence pendante, mais le texte standard, même en C++ 17, ne fournit pas d'explication sans ambiguïté sur la raison.
On prétend souvent que l'extension de la durée de vie ne s'applique que lorsque la référence se lie "directement" au temporaire, mais la norme n'a jamais rien dit à cet effet. En effet, la norme définit ce que signifie pour une référence de "lier directement", et cette définition ( par exemple , const std::string& s = "foo";
est une référence indirecte) n'est clairement pas pertinent ici.
Rakete1111 a déclaré dans un commentaire ailleurs sur SO que l'extension de durée de vie ne s'applique que lorsque la référence se lie à une valeur (plutôt qu'à une valeur gl qui a été obtenue via une référence précédente à cet objet temporaire); ils semblent dire quelque chose de similaire ici par "lié ... directement". Cependant, il n'y a aucun support textuel pour cette théorie. En effet, un code comme le suivant a parfois été considéré comme déclenchant l'extension de la durée de vie:
struct S { int x; };
const int& r = S{42}.x;
Cependant, en C++ 14, l'expression S{42}.x
est devenu une valeur x, donc si l'extension à vie s'applique ici, ce n'est pas parce que la référence se lie à une valeur.
On pourrait plutôt affirmer que l'extension de durée de vie ne s'applique qu'une seule fois et que la liaison de toute autre référence au même objet ne prolonge pas sa durée de vie. Cela expliquerait pourquoi le code de l'OP crée une référence pendant, sans empêcher l'extension de la durée de vie dans le S{42}.x
Cas. Cependant, il n'y a pas non plus de déclaration à cet effet dans la norme.
StoryTeller a également dit ici que la référence doit se lier directement, mais je ne sais pas non plus ce qu'il entend par là. Il cite un texte standard indiquant que la liaison d'une référence à un temporaire dans une instruction return
ne prolonge pas sa durée de vie. Cependant, cette déclaration semble être destinée à s'appliquer au cas où le temporaire en question est créé par l'expression complète dans la déclaration return
, car il dit que le temporaire sera détruit à la fin de cette pleine- expression. Ce n'est clairement pas le cas pour le y+1
temporaire, qui sera détruit à la fin de l'expression complète contenant l'appel à min
. Ainsi, j'ai tendance à penser que cette déclaration n'était pas destinée à s'appliquer à des cas comme celui de la question. Au lieu de cela, son effet, ainsi que les autres limitations sur l'extension de durée de vie, est d'empêcher la durée de vie d'un objet temporaire d'être étendue au-delà de la portée du bloc dans lequel il a été créé . Mais cela n'empêcherait pas le y+1
temporaire dans la question de survivre jusqu'à la fin de main
.
Ainsi, la question demeure: quel est le principe qui explique pourquoi la liaison de ref2
au temporaire dans la question ne prolonge pas la durée de vie de ce temporaire?
Le libellé du projet de travail actuel que j'ai cité plus tôt a été introduit par la résolution de CWG 1299 , qui a été ouverte en 2011 mais n'a été résolue que récemment (pas à temps pour C++ 17). Dans un sens, il clarifie l'intuition selon laquelle la référence doit se lier "directement", en délimitant les cas où la liaison est suffisamment "directe" pour que l'extension de la durée de vie se produise; il n'est cependant pas si restrictif qu'il ne l'autorise que lorsque la référence se lie à une valeur. Il permet l'extension de la durée de vie dans le S{42}.x
Cas.