En utilisant, auto&& var = <initializer>vous dites: j'accepterai n'importe quel initialiseur indépendamment du fait qu'il s'agisse d'une expression lvalue ou rvalue et je conserverai sa constance . Ceci est généralement utilisé pour le transfert (généralement avec T&&). La raison pour laquelle cela fonctionne est qu'une "référence universelle", auto&&ou T&&, se liera à n'importe quoi .
Vous pourriez dire, pourquoi ne pas simplement utiliser un const auto&parce que cela se liera également à n'importe quoi? Le problème avec l'utilisation d'une constréférence est que c'est const! Vous ne pourrez pas le lier ultérieurement à des références non const ou appeler des fonctions membres non marquées const.
À titre d'exemple, imaginez que vous vouliez obtenir un std::vector, prenez un itérateur vers son premier élément et modifiez la valeur pointée par cet itérateur d'une manière ou d'une autre:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
Ce code compilera très bien quelle que soit l'expression d'initialisation. Les alternatives pour auto&&échouer des manières suivantes:
auto => will copy the vector, but we wanted a reference
auto& => will only bind to modifiable lvalues
const auto& => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind only to rvalues
Donc pour cela, auto&&fonctionne parfaitement! Un exemple d'utilisation auto&&comme celui-ci est dans une forboucle basée sur une plage . Voir mon autre question pour plus de détails.
Si vous utilisez ensuite std::forwardsur votre auto&&référence pour préserver le fait qu'il s'agissait à l'origine d'une lvalue ou d'une rvalue, votre code dit: Maintenant que j'ai votre objet à partir d'une expression lvalue ou rvalue, je veux conserver la valeur d'origine eu pour que je puisse l'utiliser plus efficacement - cela pourrait l'invalider. Un péché:
auto&& var = some_expression_that_may_be_rvalue_or_lvalue;
// var was initialized with either an lvalue or rvalue, but var itself
// is an lvalue because named rvalues are lvalues
use_it_elsewhere(std::forward<decltype(var)>(var));
Cela permet use_it_elsewherede lui arracher les tripes pour des raisons de performances (en évitant les copies) lorsque l'initialiseur d'origine était une rvalue modifiable.
Qu'est-ce que cela signifie pour savoir si nous pouvons ou quand nous pouvons voler des ressources var? Eh bien, puisque le auto&&sera lié à quoi que ce soit, nous ne pouvons pas essayer de nous arracher varnous-mêmes les tripes - cela peut très bien être une lvalue ou même const. On peut cependant le std::forwardfaire à d'autres fonctions qui risquent de ravager totalement son intérieur. Dès que nous faisons cela, nous devrions considérer comme varétant dans un état invalide.
Appliquons maintenant ceci au cas de auto&& var = foo();, comme indiqué dans votre question, où foo renvoie une Tvaleur par. Dans ce cas, nous savons avec certitude que le type de varsera déduit comme T&&. Puisque nous savons avec certitude que c'est une rvalue, nous n'avons pas besoin de std::forwardla permission de voler ses ressources. Dans ce cas précis, sachant que cela foorenvoie par valeur , le lecteur devrait simplement le lire comme suit: je prends une référence rvalue au temporaire renvoyé foo, donc je peux m'en éloigner avec plaisir.
En guise d'addendum, je pense qu'il vaut la peine de mentionner quand une expression comme some_expression_that_may_be_rvalue_or_lvaluepourrait apparaître, autre qu'une situation «et bien votre code pourrait changer». Voici donc un exemple artificiel:
std::vector<int> global_vec{1, 2, 3, 4};
template <typename T>
T get_vector()
{
return global_vec;
}
template <typename T>
void foo()
{
auto&& vec = get_vector<T>();
auto i = std::begin(vec);
(*i)++;
std::cout << vec[0] << std::endl;
}
Voici get_vector<T>()cette belle expression qui pourrait être une lvalue ou une rvalue selon le type générique T. Nous changeons essentiellement le type de retour de get_vectorvia le paramètre de modèle de foo.
Lorsque nous appelons foo<std::vector<int>>, get_vectorretournera global_vecpar valeur, ce qui donne une expression rvalue. Alternativement, lorsque nous appelons foo<std::vector<int>&>, get_vectorretournera global_vecpar référence, résultant en une expression lvalue.
Si nous faisons:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
Nous obtenons la sortie suivante, comme prévu:
2
1
2
2
Si vous deviez changer auto&&dans le code à l' un des auto, auto&, const auto&ou const auto&&alors nous n'obtiendrons pas le résultat que nous voulons.
Une autre façon de modifier la logique du programme selon que votre auto&&référence est initialisée avec une expression lvalue ou rvalue consiste à utiliser des traits de type:
if (std::is_lvalue_reference<decltype(var)>::value) {
// var was initialised with an lvalue expression
} else if (std::is_rvalue_reference<decltype(var)>::value) {
// var was initialised with an rvalue expression
}
auto&&? J'ai réfléchi à la raison pour laquelle une boucle for basée sur une plage se développe pour être utiliséeauto&&comme exemple, mais je n'y suis pas parvenu. Peut-être que quiconque répond peut l'expliquer.