10

I have a simple function which should construct a few objects and return a vector of them while also transferring the ownership. I thought the best way of doing this is simply returning a std::vector<std::unique_ptr<int>> of the objects (let's say they are int).

When I tried the following function:

std::vector<std::unique_ptr<int>> create_stuff() {
    auto first = std::make_unique<int>(1);
    auto second = std::make_unique<int>(2);
    return {std::move(first), std::move(second)};
}

I was welcomed with a very very long compile error ending with:

xmemory0(737): error C2280: 'std::unique_ptr<int,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)':
attempting to reference a deleted function

I thought the problem was with the function itself, however the following solution worked fine:

std::vector<std::unique_ptr<int>> create_stuff() {
    auto first = std::make_unique<int>(1);
    auto second = std::make_unique<int>(2);
    std::vector<std::unique_ptr<int>> results;
    results.push_back(std::move(first));
    results.push_back(std::move(second));
    return results;
}

Why does the second solution work but not the first one? Is there a workaround that would allow me to use the short and simple syntax with the initializer list?

Boann
  • 48,794
  • 16
  • 117
  • 146
TomsonTom
  • 742
  • 7
  • 22
  • 4
    Because the initializer list gets passed and then you copy its elements in the vector, you don't move them AFAIK. – Matthieu Brucher Feb 15 '19 at 13:09
  • Instead of using `int` in your examples (since pointers to single `int` values seldom makes much sense), I suggest you create a simple class or structure to use. It doesn't have to be fully defined since that's irrelevant to the problem, but at least it would make more sense. For example using a dummy `Foo` instead of `int`. – Some programmer dude Feb 15 '19 at 13:11

2 Answers2

3

Why does the second solution work but not the first one?

List initialisation syntax that you use invokes the constructor that accepts a std::initializer_list. std::initializer_list is not movable though, and std::initializer_list<std::unique_ptr<T>> is not copiable, so invoking the constructor is not possible.

In the latter example you use the default constructor, so there is no problem.

Is there a workaround that would allow me to use the short and simple syntax with the initializer list?

You could list initialise an array, and use a pair of move iterators:

std::array arr{
    std::make_unique<int>(1),
    std::make_unique<int>(2),
};
return std::vector(
    std::make_move_iterator(std::begin(arr)),
    std::make_move_iterator(std::end(arr))
);

There was a proposal to make std::initializer_list movable, but it wasn't adopted (hasn't been adopted yet; who knows what future might bring).

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thanks, I accepted your answer. The question has just been marked as a duplicate and the linked thread offers the same workaround. Thanks for a link to the proposal! – TomsonTom Feb 15 '19 at 13:38
1

On my platform, the relevant code is:

  vector(initializer_list<value_type> __l,
   const allocator_type& __a = allocator_type())
  : _Base(__a)
  {   _M_range_initialize(__l.begin(), __l.end(),
          random_access_iterator_tag());
  }

And _M_range_initialize() simply copies from the iterator, rather than moving-from.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103