Posso elencare-inizializzare un vettore di tipo move-only?

Se passo il codice seguente attraverso il mio snapshot GCC 4.7, prova a copiare i unique_ptr nel vettore.

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

Ovviamente non può funzionare perché std::unique_ptr non è std::unique_ptr :

errore: uso della funzione eliminata ‘std :: unique_ptr :: unique_ptr (const std :: unique_ptr &) [con _Tp = int; _Dp = std :: default_delete; std :: unique_ptr = std :: unique_ptr] ‘

GCC è corretto nel tentativo di copiare i puntatori dall’elenco di inizializzazione?

La sinossi di in 18.9 rende ragionevolmente chiaro che gli elementi di un elenco di inizializzazione vengono sempre passati tramite const-reference. Sfortunatamente, non sembra esserci alcun modo di usare move-semantic negli elementi dell’elenco di inizializzazione nella revisione corrente della lingua.

Nello specifico, abbiamo:

 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 

Modifica: Dal momento che @Johannes non sembra voler pubblicare la soluzione migliore come risposta, lo farò.

 #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))}; } 

Gli iteratori restituiti da std::make_move_iterator spostano l’elemento puntato verso il riferimento quando viene sottoposto a dereferenziazione.


Risposta originale: utilizzeremo un piccolo tipo di aiuto qui:

 #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; 

Purtroppo, il codice straight-forward qui non funzionerà:

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

Poiché lo standard, per qualsiasi motivo, non definisce un costruttore di copia di conversione come questo:

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

L’ initializer_list> creato da brace-init-list ( {...} ) non verrà convertito in initializer_list che il vector assume. Quindi abbiamo bisogno di un’inizializzazione in due passaggi qui:

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

Come accennato in altre risposte, il comportamento di std::initializer_list è quello di contenere oggetti per valore e non consentire lo spostamento, quindi non è ansible. Ecco una ansible soluzione alternativa, utilizzando una chiamata di funzione in cui gli inizializzatori vengono forniti come argomenti variadici:

 #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{}); } 

Sfortunatamente multi_emplace(foos, {}); fallisce perché non può dedurre il tipo per {} , quindi per gli oggetti da build di default devi ripetere il nome della class. (o usa vector::resize )

Usando il trucco di Johannes Schaub di std::make_move_iterator() con std::experimental::make_array() , puoi usare una funzione helper:

 #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 !! } 

Guardalo dal vivo su Coliru .

Forse qualcuno può sfruttare l’inganno di std::make_array() per consentire a make_vector() di fare la sua cosa direttamente, ma non ho visto come (più precisamente, ho provato quello che pensavo dovesse funzionare, fallito e spostato). In ogni caso, il compilatore dovrebbe essere in grado di allineare l’array alla trasformazione vettoriale, come fa Clang con O2 su GodBolt .

Come è stato sottolineato, non è ansible inizializzare un vettore di tipo move-only con un elenco di inizializzatori. La soluzione originariamente proposta da @Johannes funziona bene, ma ho un’altra idea … E se non creiamo un array temporaneo e poi spostiamo elementi da lì nel vettore, ma usiamo il new posizionamento per inizializzare questo array al posto di il blocco di memoria del vettore?

Ecco la mia funzione per inizializzare un vettore di unique_ptr usando un pacchetto di argomenti:

 #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; } }