How can I construct a unique_ptr pointing to a vector with single element conveniently?

1.2k views Asked by At

I would like to construct a unique_ptr holding a vector<string>, which contains single element only. Is it possible to do this in single line of code? I try this approach:

auto values = make_unique<vector<string>>({value});

But compiler (clang++) complains:

No matching function for call to 'make_unique' candidate template ignored: 
substitution failure [with _Tp = std::__1::vector<std::__1::basic_string<char>, std::__1::allocator<std::__1::basic_string<char> > >]: no type named '__unique_array_unknown_bound' in 'std::__1::__unique_if<st... candidate template ignored: substitution failure [with _Tp = std::__1::vector<std::__1::basic_string<char>, std::__1::allocator<std::__1::basic_string<char> > >]: no type named '__unique_array_known_bound' in 'std::__1::__unique_if<std:... candidate function [with _Tp = std::__1::vector<std::__1::basic_string<char>, std::__1::allocator<std::__1::basic_string<char> > >, _Args = <>] not viable: requires 0 arguments, but 1 was provided

I don't understand why it doesn't work since vector apparently accepts using initializer_list for construction.

string value = ...;
auto values = vector<string>({value}); // this works

Is creating a unique_ptr<vector<string>> and calling its push_back the only viable approach for constructing such a unique_ptr?

auto values = make_unique<vector<string>>();
values->push_back(value);

More broadly, if I have a fixed number of elements (for example, 3 elements), and would like to build a unique_ptr holding a vector with these elements, what is the idiomatic way to do it?

2

There are 2 answers

0
N. Shead On BEST ANSWER

{...} appearing in a function call is not an initializer list (unless the function explicitly asks for a std::initializer_list); it's only an initializer list if it's being used to directly initialize something (e.g. on the right-hand-side of an assignment, or in a range-for loop).

If you really want to pass an initializer list, you can do so explicitly:

auto values = std::make_unique<std::vector<std::string>>(
  std::initializer_list<std::string>{ value });

You could also just construct a temporary vector and pass that in instead:

auto values = std::make_unique<std::vector<std::string>>(std::vector<std::string>{ value });

Or using C++17 CTAD:

auto values = std::make_unique<std::vector<std::string>>(std::vector{ value });

This will generalise to any number of fixed elements. If you just want a vector with three elements but don't care about the values, you can use std::vector's "count" constructor:

auto values = std::make_unique<std::vector<std::string>>(3);
// values contains three empty strings
0
Artyer On

If value is a local variable, you can use the iterator constructor. Otherwise, you can directly use new to use the initialiser list constructor.

using string_vec = std::vector<std::string>;

auto values = std::make_unique<string_vec>(&value, &value + 1);
auto values = std::unique_ptr<string_vec>(new string_vec{ value });

And of course the second way easily generalises to small fixed-size initializers, like new string_vec{ value_1, value_2, value_3 }. With the first way, you would have to store them in a std::string[3] and just pass the begin/end of that.