Cela ne fonctionnera pas comme indiqué, car list.begin()
a un type const T *
et il n'y a aucun moyen de vous déplacer à partir d'un objet constant. Les concepteurs de langage ont probablement fait cela pour permettre aux listes d'initialiseurs de contenir par exemple des constantes de chaîne, à partir desquelles il serait inapproprié de se déplacer.
Cependant, si vous êtes dans une situation où vous savez que la liste d'initialisation contient des expressions rvalue (ou que vous voulez forcer l'utilisateur à les écrire), alors il y a une astuce qui le fera fonctionner (j'ai été inspiré par la réponse de Sumant pour ceci, mais la solution est bien plus simple que celle-là). Vous avez besoin que les éléments stockés dans la liste d'initialisation ne soient pas des T
valeurs, mais des valeurs qui encapsulent T&&
. Ensuite, même si ces valeurs elles-mêmes sont const
qualifiées, elles peuvent toujours récupérer une rvalue modifiable.
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
};
Maintenant, au lieu de déclarer un initializer_list<T>
argument, vous déclarez un initializer_list<rref_capture<T> >
argument. Voici un exemple concret, impliquant un vecteur de std::unique_ptr<int>
pointeurs intelligents, pour lequel seule la sémantique de déplacement est définie (donc ces objets eux-mêmes ne peuvent jamais être stockés dans une liste d'initialisation); pourtant la liste des initialiseurs ci-dessous se compile sans problème.
#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;
}
Une question nécessite une réponse: si les éléments de la liste d'initialisation doivent être de vraies valeurs prvalues (dans l'exemple ce sont des valeurs x), le langage garantit-il que la durée de vie des temporels correspondants s'étend jusqu'au point où ils sont utilisés? Franchement, je ne pense pas que l'article 8.5 pertinent de la norme traite du tout de cette question. Cependant, à la lecture de 1.9: 10, il semblerait que l' expression complète pertinente dans tous les cas englobe l'utilisation de la liste d'initialiseurs, donc je pense qu'il n'y a aucun danger de suspendre les références rvalue.
initializer_list<T>
sont pas -const. Comme,initializer_list<int>
fait référence auxint
objets. Mais je pense que c'est un défaut - il est prévu que les compilateurs puissent allouer statiquement une liste en mémoire morte.