Ao usar o que auto&& var = <initializer>você está dizendo: Aceitarei qualquer inicializador, independentemente de ser uma expressão lvalue ou rvalue, e preservarei sua constância . Isso geralmente é usado para encaminhamento (geralmente com T&&). A razão pela qual isso funciona é porque uma "referência universal", auto&&ou T&&, se ligará a qualquer coisa .
Você pode dizer, bem, por que não usar um const auto&porque isso também se vincula a alguma coisa? O problema de usar uma constreferência é que é const! Mais tarde, você não poderá vinculá-lo a nenhuma referência que não seja const nem chamar funções de membros que não estejam marcadas const.
Como exemplo, imagine que você deseja obter a std::vector, leve um iterador ao seu primeiro elemento e modifique o valor apontado por esse iterador de alguma forma:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
Esse código será compilado corretamente, independentemente da expressão do inicializador. As alternativas para auto&&falhar das seguintes maneiras:
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
Então, para isso, auto&&funciona perfeitamente! Um exemplo de uso auto&&como este está em um forloop baseado em intervalo . Veja minha outra pergunta para mais detalhes.
Se você usar std::forwardsua auto&&referência para preservar o fato de que era originalmente um lvalue ou um rvalue, seu código diz: Agora que obtive seu objeto de uma expressão lvalue ou rvalue, desejo preservar a valor que ele originalmente para que eu possa usá-lo com mais eficiência - isso pode invalidá-lo. Como em:
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));
Isso permite use_it_elsewhereextrair suas tripas por uma questão de desempenho (evitando cópias) quando o inicializador original tinha um valor modificável.
O que isso significa se podemos ou quando podemos roubar recursos var? Bem, como a auto&&vontade se liga a qualquer coisa, não podemos tentar arrancar a varcoragem de nós mesmos - pode muito bem ser um valor ou até uma const. No entanto, podemos std::forwardfazê-lo para outras funções que podem devastar totalmente seu interior. Assim que fizermos isso, devemos considerar varum estado inválido.
Agora vamos aplicar isso ao caso de auto&& var = foo();, como indicado na sua pergunta, em que foo retorna a Tpor valor. Nesse caso, sabemos com certeza que o tipo de varserá deduzido como T&&. Como sabemos com certeza que é um valor, não precisamos std::forwardda permissão para roubar seus recursos. Nesse caso específico, sabendo que fooretorna por valor , o leitor deve apenas lê-lo da seguinte maneira: Estou fazendo uma referência de valor-valor ao temporário retornado de foo, para que eu possa mover-me com prazer.
Como um adendo, acho que vale a pena mencionar quando uma expressão como some_expression_that_may_be_rvalue_or_lvaluepode aparecer, além de uma situação "bem, seu código pode mudar". Então, aqui está um exemplo artificial:
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;
}
Aqui get_vector<T>()está essa expressão adorável que pode ser um lvalue ou rvalue, dependendo do tipo genérico T. Nós basicamente alteramos o tipo de retorno get_vectoratravés do parâmetro template de foo.
Quando chamamos foo<std::vector<int>>, get_vectorretornará global_vecpor valor, o que fornece uma expressão rvalue. Como alternativa, quando chamamos foo<std::vector<int>&>, get_vectorretornará global_vecpor referência, resultando em uma expressão lvalue.
Se nós fizermos:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
Obtemos a seguinte saída, conforme o esperado:
2
1
2
2
Se você fosse para mudar o auto&&no código para qualquer um auto, auto&, const auto&, ou const auto&&então nós não vai conseguir o resultado que queremos.
Uma maneira alternativa de alterar a lógica do programa com base no fato de sua auto&&referência ser inicializada com uma expressão lvalue ou rvalue é usar características de tipo:
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&&? Eu estive pensando em analisar por que um loop for baseado em intervalo se expande para usarauto&&como exemplo, mas ainda não chegou a esse ponto. Talvez quem quer que responda possa explicar.