Esto no funcionará como se indica, porque list.begin()
tiene tipo const T *
y no hay forma de que pueda moverse desde un objeto constante. Los diseñadores del lenguaje probablemente lo hicieron así para permitir que las listas de inicializadores contengan, por ejemplo, constantes de cadena, de las que no sería apropiado moverse.
Sin embargo, si se encuentra en una situación en la que sabe que la lista de inicializadores contiene expresiones rvalue (o si desea obligar al usuario a escribirlas), existe un truco que lo hará funcionar (me inspiré en la respuesta de Sumant para esto, pero la solución es mucho más simple que esa). Necesita que los elementos almacenados en la lista de inicializadores no sean T
valores, sino valores que encapsulan T&&
. Entonces, incluso si esos valores en sí mismos están const
calificados, aún pueden recuperar un rvalue modificable.
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
};
Ahora, en lugar de declarar un initializer_list<T>
argumento, declara un initializer_list<rref_capture<T> >
argumento. Aquí hay un ejemplo concreto, que involucra un vector de std::unique_ptr<int>
punteros inteligentes, para el cual solo se define la semántica de movimiento (por lo que estos objetos en sí mismos nunca pueden almacenarse en una lista de inicializadores); sin embargo, la lista de inicializadores a continuación se compila sin 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;
}
Una pregunta necesita una respuesta: si los elementos de la lista de inicializadores deben ser valores verdaderos (en el ejemplo son valores x), ¿el lenguaje asegura que la vida útil de los temporales correspondientes se extienda hasta el punto en el que se utilizan? Francamente, no creo que la sección 8.5 relevante de la norma aborde este problema en absoluto. Sin embargo, al leer 1.9: 10, parecería que la expresión completa relevante en todos los casos abarca el uso de la lista de inicializadores, por lo que creo que no hay peligro de que cuelguen referencias de rvalue.
initializer_list<T>
son no -const. Me gusta, seinitializer_list<int>
refiere aint
objetos. Pero creo que es un defecto: se pretende que los compiladores puedan asignar estáticamente una lista en la memoria de solo lectura.