83

I am trying to initialise an std::vector<std::unique_ptr<std::string>> in a way that is equivalent to an example from Bjarne Stroustrup's C++11 FAQ:

using namespace std;
vector<unique_ptr<string>> vs { new string{"Doug"}, new string{"Adams"} }; // fails
unique_ptr<string> ps { new string{"42"} }; // OK

I can see no reason why this syntax should fail. Is there something wrong with this way of initializing the container?
The compiler error message is huge; the relevant segment I find is below:

/usr/lib/gcc-snapshot/lib/gcc/i686-linux-gnu/4.7.0/../../../../include/c++/4.7.0 /bits/stl_construct.h:77:7: error: no matching function for call to 'std::unique_ptr<std::basic_string<char> >::unique_ptr(std::basic_string<char>&)'

What is the way to fix this error ?

NHDaly
  • 7,390
  • 4
  • 40
  • 45
juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • 3
    It is picking up the input iterator ctor – PlasmaHH Mar 08 '12 at 13:24
  • Very similar to http://stackoverflow.com/a/9504162/841108 – Basile Starynkevitch Mar 08 '12 at 13:28
  • @PlasmaHH In my actual code I had many entries in the initializer list, so I don't believe this is the issue. – juanchopanza Mar 08 '12 at 13:38
  • @juanchopanza: It is the issue in the code you pasted here, which you can easily see by tracing back the instantiation traces. Of course we can't say anything to code you have not presented here. – PlasmaHH Mar 08 '12 at 13:43
  • @PlasmaHH That's palusible. The code pasted is the example from the C++11 FAQ I referenced. But if I drop the unique_ptrs and use bare string pointers, the two argument initializer list works fine. – juanchopanza Mar 08 '12 at 13:53
  • @PlasmaHH from the standard, 8.5.4, "[Note: Initializer-list constructors are favored over other constructors in list-initialization (13.3.1.7). — end note ]", so I guess that should account for the input iterator contstuctor possibility. – juanchopanza Mar 08 '12 at 14:17
  • @juanchopanza: Its not about possibilities, its about what gcc actually does, and what you can easily see in the instantiation trace. C++11 is quite new, and gcc is far from implementing everything correctly, so it might or might not be doing this right. But in this case I assume gcc is right, since afaik vector only has some initializer_list> ctor, so an initializer_list will not match. – PlasmaHH Mar 08 '12 at 14:27
  • So is the code in Mr. Stroustrup's FAQ incorrect? Is it standards-compliant but GCC implemented it wrong, or is the code he posted incorrect? – NHDaly Apr 04 '15 at 00:30
  • @NHDaly It is incorrect (see the chosen answer.) – juanchopanza Apr 04 '15 at 05:29

3 Answers3

97

unique_ptr's constructor is explicit. So you can't create one implicitly with from new string{"foo"}. It needs to be something like unique_ptr<string>{ new string{"foo"} }.

Which leads us to this

// not good
vector<unique_ptr<string>> vs {
    unique_ptr<string>{ new string{"Doug"} },
    unique_ptr<string>{ new string{"Adams"} }
};

However it may leak if one of the constructors fails. It's safer to use make_unique:

// does not work
vector<unique_ptr<string>> vs {
     make_unique<string>("Doug"),
     make_unique<string>("Adams")
};

But... initializer_lists always perform copies, and unique_ptrs are not copyable. This is something really annoying about initializer lists. You can hack around it, or fallback to initialization with calls to emplace_back.

If you're actually managing strings with smart pointers and it's not just for the example, then you can do even better: just make a vector<string>. The std::string already handles the resources it uses.

Jay Bazuzi
  • 45,157
  • 15
  • 111
  • 168
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • @Xeo, indeed, http://stackoverflow.com/questions/6804216/how-to-initialize-a-container-of-noncopyable-with-initializer-list – juanchopanza Mar 08 '12 at 13:54
  • +1 for the links! And no, I am not actually concerned about making a vector of unique_ptrs to string. It is just the example from the FAQ. – juanchopanza Mar 08 '12 at 14:35
  • 5
    The first example will not leak. The `,` in the initializer list is a sequence point (in the new, somewhat more cumbersome terminology, in `{ a, b }`, `a` is sequenced before `b`). See C++11 §8.5.4/4. It's still a good idea to use `make_unique`, though. – James McNellis Mar 09 '12 at 01:28
  • 16
    Is there any reason why `std::initializer_list` can't have move semantics like any other object? – void.pointer Aug 25 '15 at 14:57
2

This question now has a better answer, at least in C++17 (C++11 will require a bit more effort). Since this is the first google result when I look for "initializing a vector of unique_ptr", I figured it's worth updating with a solution. Instead of using an initializer list, you can use a variadic function. It's an 8-line gnarly little utility function that looks like this:

#include<memory>
#include<vector>
#include<type_traits>

template <class T> auto move_to_unique(T&& t) {
  return std::make_unique<std::remove_reference_t<T>>(std::move(t));
}
template <class V, class ... Args> auto make_vector_unique(Args ... args) {
  std::vector<std::unique_ptr<V>> rv;
  (rv.push_back(move_to_unique(args)), ...);
  return rv;
}

We can now make a vector with an intuitive syntax:

auto vs = make_vector_unique<std::string>(std::string{"Doug"}, std::string{"Adam"});

You can even use it to make a vector of derived class objects.

class B {};
class D : public B {};

auto vb = make_vector_unique<B>(D{}, D{}, D{});
Samee
  • 796
  • 5
  • 13
2

After "fixing" your example:

#include <vector>
#include <memory>
#include <string>

int main()
{
    std::vector<std::unique_ptr<std::string>> vs = { { new std::string{"Doug"} }, { new std::string{"Adams"} } }; // fails
    std::unique_ptr<std::string> ps { new std::string{"42"} }; // OK
}

I got very a clear error message:

error: converting to 'std::unique_ptr<std::basic_string<char> >' from initializer list would use explicit constructor 'std::unique_ptr<_Tp, _Dp>::unique_ptr(std::unique_ptr<_Tp, _Dp>::pointer) [with _Tp = std::basic_string<char>, _Dp = std::default_delete<std::basic_string<char> >, std::unique_ptr<_Tp, _Dp>::pointer = std::basic_string<char>*]'

This error tells us that it is not possible to use the unique_ptr's explicit contructor!

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • 12
    Aaaand... that error message tells us nothing. *Why* is it not possible to use the explicit ctor? – Xeo Mar 08 '12 at 13:31
  • @Xeo - which bit of that work *explicitly* call the ctor? There might be an implicit call there, but that's forbidden. – Flexo Mar 08 '12 at 13:37
  • 6
    The semantics of a unique_ptr is that it can not be copied, thats the reason the constructor is deleted. But shouldn't `std::vector` be move-aware and so be able to move from a temporary to the vector? – Gunther Piez Mar 08 '12 at 13:41
  • @drhirch It cannot be copy constructed, but it can be explicitly constructed a from string. So the error is that what is being attempted here is an implicit construction from a string pointer. – juanchopanza Mar 08 '12 at 15:13
  • @VJovic I think the error is that it is not possible to implicitly construct the unique_ptr from a string pointer, so I would say that "the error says that it is not possible to implicitly construct unique_ptr because the constructor is explicit. Would you agree? – juanchopanza Mar 08 '12 at 15:15