6
votes

I try to construct a vector with unique_ptr. But I do not find a direct way. The following code does not compiles. The error is:Call to implicitly-deleted copy constructor of 'std::__1::unique_ptr >':

#include <iostream>
#include <memory>
#include <utility>
#include <vector>
class test1{
public:
    test1(){};
    test1(test1&&)=default;
};

int main(int argc, const char * argv[]) {
    std::unique_ptr<test1> us(new test1());
    std::vector<std::unique_ptr<test1>> vec{move(us)};
    return 0;
}
2

2 Answers

4
votes

This make_vector is a function that takes any number of arguments, and perfect-forwards them into a vector.

// get the first type in a pack, if it exists:
template<class...Ts>
struct first {};
template<class T, class...Ts>
struct first<T,Ts...>{
  using type=T;
};
template<class...Ts>
using first_t=typename first<Ts...>::type;

// build the return type:
template<class T0, class...Ts>
using vector_T = 
  typename std::conditional<
    std::is_same<T0, void>::value,
    typename std::decay<first_t<Ts...>>::type,
    T0
  >::type;
template<class T0, class...Ts>
using vector_t = std::vector< vector_T<T0, Ts...> >;

// make a vector, non-empty arg case:
template<class T0=void, class...Ts, class R=vector_t<T0, Ts...>>
R make_vector( Ts&&...ts ) {
  R retval;
  retval.reserve(sizeof...(Ts)); // we know how many elements
  // array unpacking trick:
  using discard = int[];
  (void)discard{0,((
    retval.emplace_back( std::forward<Ts>(ts) )
  ),void(),0)...};
  return retval; // NRVO!
}
// the empty overload:
template<class T>
std::vector<T> make_vector() {
  return {};
}

use:

std::vector<std::unique_ptr<test1>> vec =
  make_vector(
    std::move(u1), std::move(u2)
  );

live example

I polished it a bit. It will deduce the return type if you pass it 1 or more args and you don't pass it a type. If you pass it a type, it will use that type. If you fail to pass it a type or any args, it will complain. (if you forward packs, or are storing it in a particular type, I'd always give it a type).


A further step could be done, where we do return type deduction to eliminate the requirement to specify the type even in the empty case. This may be required in your use case, I don't know, but it matches how you don't need to specify the type of a {}, so I thought I'd toss it out there:

template<class...Ts>
struct make_vec_later {
  std::tuple<Ts...> args; // could make this `Ts&&...`, but that is scary

   // make this && in C++14
  template<class T, size_t...Is>
  std::vector<T> get(std::index_sequence<Is...>) {
    return make_vector<T>(std::get<Is>(std::move(args))...);
  }

  // make this && in C++14
  template<class T>
  operator std::vector<T>(){
    return std::move(*this).template get<T>( std::index_sequence_for<Ts...>{} );
  }
};
template<class...Ts>
make_vec_later<Ts...> v(Ts&&...ts) {
  return {std::tuple<Ts...>(std::forward<Ts>(ts)...)};
}

this does rely on a C++14 feature of index_sequence, but those are easy to rewrite in C++11 if your compiler doesn't have it yet. Simply google it on stack overflow, there are a myriad of implementations.

Now the syntax looks like:

std::vector<std::unique_ptr<test1>> vec =
  v(std::move(u1));

where the list of arguments can be empty.

Live example

Supporting variant allocators is left as an exercise to the user. Add another type to make_vector called A, and have it default to void. If it is void, swap it for std::allocator<T> for whatever type T is chosen for the vector. In the return type deduction version, do something similar.

5
votes

You're calling the vector constructor ((7) on the linked page) that takes an initializer_list<T> argument. An initializer_list only allows const access to its elements, so the vector must copy the elements, and this, of course, fails to compile.

The following should work

std::unique_ptr<test1> us(new test1());
std::vector<std::unique_ptr<test1>> vec;

vec.push_back(move(us));
// or
vec.push_back(std::unique_ptr<test1>(new test1()));
// or
vec.push_back(std::make_unique<test1>()); // make_unique requires C++14

You could use the vector constructor that takes two iterators, but the solution is still not a one-liner because it requires you to define a temporary array that you can then move from.

std::unique_ptr<test1> arr[] = {std::make_unique<test1>()};
std::vector<std::unique_ptr<test1>> vec{std::make_move_iterator(std::begin(arr)),
                                        std::make_move_iterator(std::end(arr))};