Isso não funcionará como declarado, porque list.begin()
tem tipo const 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 encapsulam T&&
. Então, mesmo que esses próprios valores sejam const
qualificados, eles ainda podem recuperar um rvalue modificável.
template<typename T>
class rref_capture
{
T* ptr;
public:
rref_capture(T&& x) : ptr(&x) {}
operator T&& () const { return std::move(*ptr); } // restitute rvalue ref
};
Agora, em vez de declarar um initializer_list<T>
argumento, você declara um initializer_list<rref_capture<T> >
argumento. Aqui está um exemplo concreto, envolvendo um vetor de std::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.
#include <memory>
#include <initializer_list>
class uptr_vec
{
typedef std::unique_ptr<int> uptr; // move only type
std::vector<uptr> data;
public:
uptr_vec(uptr_vec&& v) : data(std::move(v.data)) {}
uptr_vec(std::initializer_list<rref_capture<uptr> > l)
: data(l.begin(),l.end())
{}
uptr_vec& operator=(const uptr_vec&) = delete;
int operator[] (size_t index) const { return *data[index]; }
};
int main()
{
std::unique_ptr<int> a(new int(3)), b(new int(1)),c(new int(4));
uptr_vec v { std::move(a), std::move(b), std::move(c) };
std::cout << v[0] << "," << v[1] << "," << v[2] << std::endl;
}
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.
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.