Se você ler código como
auto&& var = foo();
onde foo
é qualquer função que retorna pelo valor do tipo T
. Então var
é um lvalue do tipo rvalue de referência T
. Mas o que isso implica var
? Isso significa que estamos autorizados a roubar os recursos var
? Existem situações razoáveis em que você deve auto&&
informar ao leitor do seu código algo parecido com o que você faz quando retorna a unique_ptr<>
para informar que possui propriedade exclusiva? E o que dizer, por exemplo, T&&
quando T
é do tipo classe?
Eu só quero entender, se existem outros casos de uso auto&&
além daqueles na programação de modelos; como os discutidos nos exemplos deste artigo, Referências universais de Scott Meyers.
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.foo
retornos, armazenando um valor de referência a ele como UB a ne.foo
pode, por exemplo parecido:int foo(){return 1;}
.Respostas:
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 comT&&
). A razão pela qual isso funciona é porque uma "referência universal",auto&&
ouT&&
, 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 umaconst
referê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 marcadasconst
.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:Esse código será compilado corretamente, independentemente da expressão do inicializador. As alternativas para
auto&&
falhar das seguintes maneiras:Então, para isso,
auto&&
funciona perfeitamente! Um exemplo de usoauto&&
como este está em umfor
loop baseado em intervalo . Veja minha outra pergunta para mais detalhes.Se você usar
std::forward
suaauto&&
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:Isso permite
use_it_elsewhere
extrair 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 aauto&&
vontade se liga a qualquer coisa, não podemos tentar arrancar avar
coragem de nós mesmos - pode muito bem ser um valor ou até uma const. No entanto, podemosstd::forward
fazê-lo para outras funções que podem devastar totalmente seu interior. Assim que fizermos isso, devemos considerarvar
um estado inválido.Agora vamos aplicar isso ao caso de
auto&& var = foo();
, como indicado na sua pergunta, em que foo retorna aT
por valor. Nesse caso, sabemos com certeza que o tipo devar
será deduzido comoT&&
. Como sabemos com certeza que é um valor, não precisamosstd::forward
da permissão para roubar seus recursos. Nesse caso específico, sabendo quefoo
retorna por valor , o leitor deve apenas lê-lo da seguinte maneira: Estou fazendo uma referência de valor-valor ao temporário retornado defoo
, 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_lvalue
pode aparecer, além de uma situação "bem, seu código pode mudar". Então, aqui está um exemplo artificial:Aqui
get_vector<T>()
está essa expressão adorável que pode ser um lvalue ou rvalue, dependendo do tipo genéricoT
. Nós basicamente alteramos o tipo de retornoget_vector
através do parâmetro template defoo
.Quando chamamos
foo<std::vector<int>>
,get_vector
retornaráglobal_vec
por valor, o que fornece uma expressão rvalue. Como alternativa, quando chamamosfoo<std::vector<int>&>
,get_vector
retornaráglobal_vec
por referência, resultando em uma expressão lvalue.Se nós fizermos:
Obtemos a seguinte saída, conforme o esperado:
Se você fosse para mudar o
auto&&
no código para qualquer umauto
,auto&
,const auto&
, ouconst 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:fonte
T vec = get_vector<T>();
função interna foo? Ou eu estou simplificando-o a um nível absurdo :)Primeiro, recomendo a leitura desta resposta como uma leitura lateral para uma explicação passo a passo sobre como funciona a dedução de argumentos de modelo para referências universais.
Não necessariamente. E se,
foo()
de repente, retornasse uma referência ou você alterasse a chamada, mas se esquecesse de atualizar o uso devar
? Ou se você estiver no código genérico e o tipo de retorno defoo()
pode mudar, dependendo dos seus parâmetros?Pense
auto&&
em ser exatamente o mesmo que oT&&
intemplate<class T> void f(T&& v);
, porque é (quase † ) exatamente isso. O que você faz com referências universais em funções, quando precisa transmiti-las ou usá-las de alguma forma? Você usastd::forward<T>(v)
para recuperar a categoria de valor original. Se era um lvalue antes de ser passado para sua função, ele permanece um lvalue após a passagemstd::forward
. Se fosse um rvalue, ele se tornará um rvalue novamente (lembre-se, uma referência nomeada rvalue é um lvalue).Então, como você usa
var
corretamente de maneira genérica? Usestd::forward<decltype(var)>(var)
. Isso funcionará exatamente da mesma forma questd::forward<T>(v)
no modelo de função acima. Sevar
for aT&&
, você receberá um valor de volta e, se forT&
, receberá um valor de volta.Então, voltando ao tópico: O que
auto&& v = f();
estd::forward<decltype(v)>(v)
em uma base de código nos dizem? Eles nos dizem quev
serão adquiridos e repassados da maneira mais eficiente. Lembre-se, porém, que depois de ter encaminhado essa variável, é possível que ela seja movida de, portanto, seria incorreto usá-la ainda mais sem redefini-la.Pessoalmente, uso
auto&&
no código genérico quando preciso de uma variável modificável . O encaminhamento perfeito de um rvalue está sendo modificado, pois a operação de movimentação potencialmente rouba suas entranhas. Se eu apenas quero ser preguiçoso (ou seja, não soletrar o nome do tipo, mesmo que eu saiba) e não precisar modificar (por exemplo, ao imprimir apenas elementos de um intervalo), eu continuareiauto const&
.†
auto
é na medida diferente queauto v = {1,2,3};
vai fazerv
umstd::initializer_list
, enquantof({1,2,3})
será um fracasso dedução.fonte
foo()
retornar um tipo de valorT
,var
(esta expressão) será um lvalue e seu tipo (dessa expressão) será uma referência de rvalue paraT
(ieT&&
).Considere algum tipo
T
que tenha um construtor de movimento e assumausa esse construtor de movimento.
Agora, vamos usar uma referência intermediária para capturar o retorno de
foo
:isso exclui o uso do construtor move, então o valor de retorno terá que ser copiado em vez de movido (mesmo se usarmos
std::move
aqui, na verdade não podemos mover através de uma const ref)No entanto, se usarmos
o construtor de movimentação ainda está disponível.
E para resolver suas outras perguntas:
A primeira coisa, como diz Xeo, é basicamente passar o X da maneira mais eficiente possível , seja qual for o tipo X. Portanto, ver o código que usa
auto&&
internamente deve comunicar que ele usará a semântica de movimentação internamente, quando apropriado.Quando um modelo de função usa um argumento do tipo
T&&
, está dizendo que pode mover o objeto que você passa. Retornarunique_ptr
explicitamente dá propriedade ao chamador; aceitarT&&
pode remover a propriedade do chamador (se existir um movedor, etc.).fonte
ref
ervref
são ambos os valores. Se você quiser o construtor de movimentação, precisará escreverT t(std::move(rvref))
.auto const &
:?auto&&
e o que diz ao leitor do seu código usandoauto&&
?A
auto &&
sintaxe usa dois novos recursos do C ++ 11:A
auto
parte permite que o compilador deduza o tipo com base no contexto (o valor de retorno neste caso). Este é, sem quaisquer qualificações de referência (o que lhe permite especificar se desejaT
,T &
ouT &&
para um tipo deduzidaT
).A
&&
é a nova semântica de movimento. Uma semântica de suporte ao tipo implementa um construtorT(T && other)
que move o conteúdo da melhor maneira possível. Isso permite que um objeto troque a representação interna em vez de executar uma cópia detalhada.Isso permite que você tenha algo como:
Assim:
executará uma cópia do vetor retornado (caro), mas:
trocará a representação interna do vetor (o vetor de
foo
e o vetor vazio devar
), portanto será mais rápido.Isso é usado na nova sintaxe de loop for:
Onde o loop for retém um
auto &&
valor de retornofoo
eitem
é uma referência para cada valor emfoo
.fonte
auto&&
não moverá nada, apenas fará uma referência. Seja uma referência lvalue ou rvalue, depende da expressão usada para inicializá-la.std::vector
estd::string
são moveconstructible. Isso não tem nada a ver com o tipo devar
.