Puis-je initialiser par liste un vecteur de type Move-only?

Si je transmets le code suivant via mon instantané GCC 4.7, il tente de copier le unique_ptr dans le vecteur.

 #include  #include  int main() { using move_only = std::unique_ptr; std::vector v { move_only(), move_only(), move_only() }; } 

Évidemment, cela ne peut pas fonctionner car std::unique_ptr n’est pas copiable:

erreur: utilisation de la fonction ‘std :: unique_ptr :: unique_ptr supprimée (const std :: unique_ptr &) [avec _Tp = int; _Dp = std :: default_delete; std :: unique_ptr = std :: unique_ptr] ‘

GCC est-il correct en essayant de copier les pointeurs de la liste d’initialisation?

Le synopsis de au 18.9 indique clairement que les éléments d’une liste d’initialisation sont toujours transmis via la référence const. Malheureusement, il ne semble y avoir aucun moyen d’utiliser move-sémantique dans les éléments de la liste d’initialisation dans la révision actuelle du langage.

Plus précisément, nous avons:

 typedef const E& reference; typedef const E& const_reference; typedef const E* iterator; typedef const E* const_iterator; const E* begin() const noexcept; // first element const E* end() const noexcept; // one past the last element 

Edit: Puisque @Johannes ne semble pas vouloir afficher la meilleure solution en réponse, je le ferai tout simplement.

 #include  #include  #include  int main(){ using move_only = std::unique_ptr; move_only init[] = { move_only(), move_only(), move_only() }; std::vector v{std::make_move_iterator(std::begin(init)), std::make_move_iterator(std::end(init))}; } 

Les iterators renvoyés par std::make_move_iterator déplaceront l’élément pointé lors du déréférencement.


Réponse originale: Nous allons utiliser un petit type d’aide ici:

 #include  #include  template struct rref_wrapper { // CAUTION - very volatile, use with care explicit rref_wrapper(T&& v) : _val(std::move(v)) {} explicit operator T() const{ return T{ std::move(_val) }; } private: T&& _val; }; // only usable on temporaries template typename std::enable_if< !std::is_lvalue_reference::value, rref_wrapper >::type rref(T&& v){ return rref_wrapper(std::move(v)); } // lvalue reference can go away template void rref(T&) = delete; 

Malheureusement, le code simple ne fonctionnera pas:

 std::vector v{ rref(move_only()), rref(move_only()), rref(move_only()) }; 

Comme la norme, pour quelque raison que ce soit, ne définit pas un constructeur de copie de conversion comme celui-ci:

 // in class initializer_list template initializer_list(initializer_list const& other); 

L’ initializer_list> créé par brace-init-list ( {...} ) ne sera pas converti en initializer_list que prend le vector . Nous avons donc besoin d’une initialisation en deux étapes ici:

 std::initializer_list> il{ rref(move_only()), rref(move_only()), rref(move_only()) }; std::vector v(il.begin(), il.end()); 

Comme mentionné dans d’autres réponses, le comportement de std::initializer_list est de conserver les objects par valeur et de ne pas autoriser le déplacement, ce n’est donc pas possible. Voici une solution possible, en utilisant un appel de fonction où les initialiseurs sont donnés sous forme d’arguments variadiques:

 #include  #include  struct Foo { std::unique_ptr u; int x; Foo(int x = 0): x(x) {} }; template // recursion-ender void multi_emplace(std::vector &vec) {} template void multi_emplace(std::vector &vec, T1&& t1, Types&&... args) { vec.emplace_back( std::move(t1) ); multi_emplace(vec, args...); } int main() { std::vector foos; multi_emplace(foos, 1, 2, 3, 4, 5); multi_emplace(foos, Foo{}, Foo{}); } 

Malheureusement multi_emplace(foos, {}); échoue car il ne peut pas déduire le type pour {} , donc pour les objects à construire par défaut, vous devez répéter le nom de la classe. (ou utilisez vector::resize )

En utilisant l’astuce de std::make_move_iterator() Johannes Schaub avec std::experimental::make_array() , vous pouvez utiliser une fonction d’assistance:

 #include  #include  #include  #include  struct X {}; template auto make_vector( std::array&& a ) -> std::vector { return { std::make_move_iterator(std::begin(a)), std::make_move_iterator(std::end(a)) }; } template auto make_vector( T&& ... t ) -> std::vector::type> { return make_vector( std::experimental::make_array( std::forward(t)... ) ); } int main() { using UX = std::unique_ptr; const auto a = std::experimental::make_array( UX{}, UX{}, UX{} ); // Ok const auto v0 = make_vector( UX{}, UX{}, UX{} ); // Ok //const auto v1 = std::vector< UX >{ UX{}, UX{}, UX{} }; // !! Error !! } 

Voyez-le vivre sur Coliru .

Peut-être que quelqu’un peut tirer parti de la std::make_array() pour permettre à make_vector() de faire son travail directement, mais je ne voyais pas comment (plus précisément, j’ai essayé ce qui devrait fonctionner, échoué et passé). Dans tous les cas, le compilateur devrait être capable d’inclure le tableau dans la transformation vectorielle, comme le fait Clang avec O2 sur GodBolt .

Comme cela a été souligné, il n’est pas possible d’initialiser un vecteur de type move-only avec une liste d’initialisation. La solution proposée à l’origine par @Johannes fonctionne bien, mais j’ai une autre idée … Et si nous ne créons pas un tableau temporaire, puis déplaçons des éléments dans le vecteur, mais utilisons le placement new pour initialiser ce tableau à la place de le bloc mémoire du vecteur?

Voici ma fonction pour initialiser un vecteur de unique_ptr utilisant un pack d’arguments:

 #include  #include  #include  /// @see http://stackoverflow.com/questions/7038357/make-unique-and-perfect-forwarding template  inline std::vector> make_vector_of_unique(Items&&... items) { typedef std::unique_ptr value_type; // Allocate memory for all items std::vector result(sizeof...(Items)); // Initialize the array in place of allocated memory new (result.data()) value_type[sizeof...(Items)] { make_unique::type>(std::forward(items))... }; return result; } int main(int, char**) { auto testVector = make_vector_of_unique(1,2,3); for (auto const &item : testVector) { std::cout < < *item << std::endl; } }