En utilisant auto&& var = <initializer>
vous dire: j'accepte toute initialiseur indépendamment du fait que c'est une lvalue ou rvalue expression et je conserve son constness. Ceci est généralement utilisé pour le transfert (généralement avec T&&
). La raison pour laquelle cela fonctionne est parce qu'un "univers de référence", auto&&
ou T&&
, va se lier à quelque chose.
Vous pourriez dire, eh bien, pourquoi ne pas simplement utiliser un const auto&
parce que cela va aussi se lier à quelque chose? Le problème avec l'aide d'un const
de référence, c'est que c'est const
! Vous ne serez pas en mesure plus tard de se lier à n'importe quel non-const références ou d'appeler l'une des fonctions membres qui ne sont pas marqués const
.
Par exemple, imaginez que vous voulez obtenir un std::vector
, prendre un itérateur vers son premier élément et modifiez la valeur pointée par l'itérateur d'une certaine façon:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
Ce code permet de compiler des beaux indépendamment de l'initialiseur d'expression. Les alternatives à l' auto&&
échouent dans l'une 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 de auto&&
comme c'est dans une gamme à base for
boucle. Voir mon autre question pour plus de détails.
Si vous utilisez ensuite std::forward
sur votre auto&&
de référence pour préserver le fait qu'il était à l'origine une lvalue ou une rvalue, votre code dit: Maintenant que j'ai votre objet soit à partir d'une lvalue ou rvalue expression, je tiens à préserver selon valueness il avait à l'origine donc je peux l'utiliser le plus efficacement possible - ce qui pourrait l'invalider. Comme dans:
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 d' use_it_elsewhere
pour ripper ses tripes pour des raisons de performance (en évitant les copies) lorsque l'original d'initialiseur était modifiable rvalue.
Qu'est-ce que cela signifie quant à savoir si nous pouvons ou quand on peut voler des ressources d' var
? Eh bien, puisque l' auto&&
vont se lier à quoi que ce soit, nous ne pouvons pas essayer d'arracher var
s tripes en nous - mêmes, elle peut très bien être une lvalue ou même const. Nous pouvons toutefois std::forward
à d'autres fonctions qui peuvent totalement ravage de ses entrailles. Dès que nous faisons cela, nous devons considérer var
à être dans un état non valide.
Maintenant, nous allons l'appliquer aux cas d' auto&& var = foo();
, comme indiqué dans votre question, où foo renvoie une T
en valeur. Dans ce cas, nous savons pour sûr que le type d' var
sera déduit qu' T&&
. Puisque nous savons que pour certains c'est une rvalue, nous n'avons pas besoin std::forward
's la permission de voler de ses ressources. Dans ce cas précis, sachant qu' foo
rendements en valeur, le lecteur doit simplement le lire: je suis prise de référence rvalue temporaire retourné à partir de foo
, de sorte que je peux heureusement déplacer.
Comme un additif, je pense qu'il vaut la peine de mentionner lorsqu'une expression comme some_expression_that_may_be_rvalue_or_lvalue
peut tourner jusqu'à, autre qu'une "eh bien, votre code peut changer" de la situation. 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;
}
Ici, get_vector<T>()
est cette belle expression qui pourrait être une lvalue ou rvalue selon le type générique T
. Nous avons essentiellement changer le type de retour d' get_vector
par le paramètre de modèle de foo
.
Lorsque nous appelons foo<std::vector<int>>
, get_vector
sera de retour global_vec
en valeur, ce qui donne une rvalue expression. Par ailleurs, lorsque nous appelons foo<std::vector<int>&>
, get_vector
sera de retour global_vec
par référence, résultant en une lvalue expression.
Si nous le 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 le résultat suivant, comme prévu:
2
1
2
2
Si vous deviez modifier l' auto&&
dans le code de l' auto
, auto&
, const auto&
ou const auto&&
alors on ne pourra pas obtenir le résultat que nous voulons.
Un autre moyen pour changer la logique du programme basé sur si votre auto&&
de référence est initialisé avec une lvalue ou rvalue expression est d'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
}