Posso mover elementos de um std::initializer_list<T>
?
#include <initializer_list>
#include <utility>
template<typename T>
void foo(std::initializer_list<T> list)
{
for (auto it = list.begin(); it != list.end(); ++it)
{
bar(std::move(*it)); // kosher?
}
}
Uma vez que std::intializer_list<T>
requer atenção especial do compilador e não tem semântica de valor como os contêineres normais da biblioteca padrão C ++, prefiro prevenir do que remediar e perguntar.
c++
templates
c++11
move-semantics
initializer-list
fredoverflow
fonte
fonte
initializer_list<T>
são não constantes. Like,initializer_list<int>
refere-se aint
objetos. Mas eu acho que é um defeito - a intenção é que os compiladores possam alocar estaticamente uma lista na memória somente leitura.Respostas:
Não, isso não funcionará como planejado; você ainda receberá cópias. Estou bastante surpreso com isso, pois pensei que
initializer_list
existia para manter uma série de temporários até que elesmove
morressem.begin
eend
parainitializer_list
retornoconst T *
, então o resultado demove
em seu código éT const &&
- uma referência rvalue imutável. Essa expressão não pode ser mudada de forma significativa. Ele será vinculado a um parâmetro de função do tipoT const &
porque rvalues se vinculam a referências const lvalue, e você ainda verá a semântica de cópia.Provavelmente a razão para isso é que o compilador pode optar por fazer
initializer_list
uma constante inicializada estaticamente, mas parece que seria mais limpo fazer seu tipoinitializer_list
ouconst initializer_list
a critério do compilador, então o usuário não sabe se deve esperar umconst
ou mutável resultado debegin
eend
. Mas isso é apenas meu pressentimento, provavelmente há um bom motivo para estar errado.Atualização: eu escrevi uma proposta ISO para
initializer_list
suporte de tipos apenas de movimento. É apenas um primeiro rascunho e ainda não foi implementado em lugar nenhum, mas você pode ver para mais análises do problema.fonte
std::move
é seguro, se não produtivo. (Excluindo osT const&&
construtores de movimento.)const std::initializer_list<T>
ou apenasstd::initializer_list<T>
de uma forma que não cause surpresas com frequência. Considere que cada argumento noinitializer_list
pode ser umconst
ou outro e que é conhecido no contexto do chamador, mas o compilador deve gerar apenas uma versão do código no contexto do chamado (ou seja, dentrofoo
dele não sabe nada sobre os argumentos que ostd::initializer_list &&
sobrecarga fizesse algo, mesmo se uma sobrecarga de não referência também fosse necessária. Suponho que ficaria ainda mais confuso do que a situação atual, que já é ruim.Não da maneira que você pretende. Você não pode mover um
const
objeto. Estd::initializer_list
apenas fornececonst
acesso aos seus elementos. Portanto, o tipo deit
éconst T *
.Sua tentativa de ligar
std::move(*it)
resultará apenas em um valor l. IE: uma cópia.std::initializer_list
faz referência à memória estática . É para isso que serve a aula. Você não pode se mover da memória estática, porque o movimento implica em mudá-la. Você só pode copiar dele.fonte
initializer_list
referência à pilha, se necessário. (Se o conteúdo não for constante, ainda é seguro para thread.)initializer_list
próprio objeto pode ser um valor x, mas seu conteúdo (a matriz real de valores para a qual ele aponta) éconst
, porque esses conteúdos podem ser valores estáticos. Você simplesmente não pode mover-se do conteúdo de uminitializer_list
.const
referenciado , produzindo um valor x.move
pode não ter sentido, mas é legal e até mesmo possível declarar um parâmetro que aceite exatamente isso. Se mover um tipo específico por acaso for um no-op, pode até funcionar corretamente.std::move
. Isso garante que você possa saber, por inspeção, quando uma operação de movimentação ocorre, uma vez que afeta tanto a origem quanto o destino (você não quer que isso aconteça implicitamente para objetos nomeados). Por causa disso, se você usarstd::move
em um lugar onde uma operação de movimentação não acontece (e nenhum movimento real acontecerá se você tiver um valorconst
x), então o código é enganoso. Acho que é um errostd::move
poder ser chamado em umconst
objeto.const &&
em uma classe de coleta de lixo com ponteiros gerenciados, onde tudo relevante era mutável e mover movia o gerenciamento de ponteiros, mas não afetava o valor contido. Sempre existem casos extremos complicados: v).Isso não funcionará como declarado, porque
list.begin()
tem tipoconst T *
e não há como mover de um objeto constante. Os designers da linguagem provavelmente fizeram isso para permitir que as listas de inicializadores contivessem, por exemplo, constantes de string, das quais seria inadequado mover.No entanto, se você estiver em uma situação em que sabe que a lista de inicializadores contém expressões rvalue (ou deseja forçar o usuário a escrevê-las), existe um truque que fará com que funcione (fui inspirado pela resposta de Sumant para isso, mas a solução é muito mais simples do que essa). Você precisa que os elementos armazenados na lista do inicializador não sejam
T
valores, mas sim valores que encapsulamT&&
. Então, mesmo que esses próprios valores sejamconst
qualificados, eles ainda podem recuperar um rvalue modificável.Agora, em vez de declarar um
initializer_list<T>
argumento, você declara uminitializer_list<rref_capture<T> >
argumento. Aqui está um exemplo concreto, envolvendo um vetor destd::unique_ptr<int>
ponteiros inteligentes, para o qual apenas a semântica de movimento é definida (portanto, esses objetos nunca podem ser armazenados em uma lista de inicializadores); ainda assim, a lista de inicializadores abaixo compila sem problemas.Uma pergunta precisa de uma resposta: se os elementos da lista de inicializadores devem ser prvalues verdadeiros (no exemplo, são valores x), a linguagem garante que o tempo de vida dos temporários correspondentes se estenda até o ponto em que são usados? Francamente, não acho que a seção 8.5 relevante da norma trate desse assunto. No entanto, lendo 1.9: 10, parece que a expressão completa relevante em todos os casos engloba o uso da lista de inicializadores, então eu acho que não há perigo de referências rvalue penduradas.
fonte
"Hello world"
? Se você mover a partir deles, apenas copie um ponteiro (ou vincule uma referência).{..}
são vinculados a referências no parâmetro de função derref_capture
. Isso não estende sua vida útil, eles ainda são destruídos no final da expressão completa em que foram criados.std::initializer_list<rref_capture<T>>
em algum traço de transformação de sua escolha - digamos,std::decay_t
- para bloquear deduções indesejadas.Achei que seria instrutivo oferecer um ponto de partida razoável para uma solução alternativa.
Comentários embutidos.
fonte
initializer_list
pode ser movido, e não se alguém tinha soluções alternativas. Além disso, o principal argumento de venda deinitializer_list
é que ele é modelado apenas no tipo de elemento, não no número de elementos e, portanto, não exige que os destinatários também sejam modelados - e isso perde completamente isso.initializer_list
mas não estão sujeitos a todas as restrições que o tornam útil. :)initializer_list
(por meio da mágica do compilador) evita ter de funções de modelo no número de elementos, algo que é inerentemente exigido por alternativas baseadas em matrizes e / ou funções variáveis, restringindo assim a gama de casos em que as últimas são utilizáveis. Pelo meu entendimento, esta é precisamente uma das principais razões para terinitializer_list
, então parecia valer a pena mencionar.Parece não permitido no padrão atual como já respondido . Aqui está outra solução alternativa para alcançar algo semelhante, definindo a função como variável em vez de obter uma lista de inicializadores.
Os modelos de variáveis podem manipular referências de valor r apropriadamente, ao contrário de initializer_list.
Neste código de exemplo, usei um conjunto de pequenas funções auxiliares para converter os argumentos variados em um vetor, para torná-lo semelhante ao código original. Mas é claro que você pode escrever uma função recursiva com modelos variadic diretamente.
fonte
initializer_list
pode ser movido, e não se alguém tinha soluções alternativas. Além disso, o principal argumento de venda deinitializer_list
é que ele é modelado apenas no tipo de elemento, não no número de elementos e, portanto, não exige que os destinatários também sejam modelados - e isso perde completamente isso.Eu tenho uma implementação muito mais simples que faz uso de uma classe de invólucro que atua como uma tag para marcar a intenção de mover os elementos. Este é um custo de tempo de compilação.
A classe wrapper é projetada para ser usada da maneira como
std::move
é usada, apenas substituastd::move
pormove_wrapper
, mas isso requer C ++ 17. Para especificações mais antigas, você pode usar um método de construtor adicional.Você precisará escrever métodos / construtores de construtor que aceitem classes de invólucro dentro
initializer_list
e mover os elementos de acordo.Se você precisar que alguns elementos sejam copiados em vez de movidos, construa uma cópia antes de passá-la para
initializer_list
.O código deve ser autodocumentado.
fonte
Em vez de usar um
std::initializer_list<T>
, você pode declarar seu argumento como uma referência de array rvalue:Veja o exemplo usando
std::unique_ptr<int>
: https://gcc.godbolt.org/z/2uNxv6fonte
Considere o
in<T>
idioma descrito em cpptruths . A idéia é determinar lvalue / rvalue em tempo de execução e então chamar move ou copy-construction.in<T>
irá detectar rvalue / lvalue mesmo que a interface padrão fornecida por initializer_list seja uma referência const.fonte