Emplacement of a vector with initializer list
Asked Answered
P

3

29

i have a std::vector<std::vector<double>> and would like to add some elements at the end of it so this was my trial:

std::vector<std::vector<double> > vec;
vec.emplace_back({0,0});

but this does not compile whereas the following will do:

std::vector<double> vector({0,0});

Why can't emplace_back construct the element at this position? Or what am i doing wrong?

Persuade answered 3/7, 2014 at 10:14 Comment(0)
S
13

Template deduction cannot guess that your brace-enclosed initialization list should be a vector. You need to be explicit:

vec.emplace_back(std::vector<double>{0.,0.});

Note that this constructs a vector, and then moves it into the new element using std::vector's move copy constructor. So in this particular case it has no advantage over push_back(). @TimKuipers 's answer shows a way to get around this issue.

Simulate answered 3/7, 2014 at 10:18 Comment(5)
thanks, but i have another question to this: Why does it work for the second example? Since the compiler has similar information about the type (emplace_back expects to get arguments for a constructor of std::vector<double> and so is the second example)Persuade
Because in the second example you call the constructor of vector, which can handle the initializer list directly.Gus
So effectively you're not emplacing, but just pushing an already constructed element, rather than constructing the vector in place.Censure
@TimKuipers Yes, indeed. This would move-copy the argument into vec.Simulate
there's no point in using emplace if you're just gonna construct the object then emplace it. Tim Kuipers answer explainsGaud
C
33

The previous answer mentioned you could get the code to compile when you construct the vector in line and emplace that. That means, however, that you are calling the move-constructor on a temporary vector, which means you are not constructing the vector in-place, while that's the whole reason of using emplace_back rather than push_back.

Instead you should cast the initializer list to an initializer_list, like so:

#include <vector>
#include <initializer_list>

int main()
{
    std::vector<std::vector<int>> vec;
    vec.emplace_back((std::initializer_list<int>){1,2});
}
Censure answered 16/11, 2016 at 12:14 Comment(6)
It would actually call the move constructor, not the copy constructor.Budde
Good catch. Edited my answer.Censure
Why do you use the old style C-cast syntax (std::initializer_list<int>){1,2} here instead of the constructor syntax std::initializer_list<int>({1,2})?Strickler
@Strickler that's a best practice question on its own. I've searched stackoverflow and found a couple of reasons, but there is no solid proof which one is the best. It seems the consensus is that both those cast styles should be avoided in favour of static_cast and the like.Censure
I think the important question remains unanswered: If I can do vector<vector<int>> a {{1,2}, {3,4}}; instead of vector<vector<int>> a {(std::initializer_list<int>){1,2}, (std::initializer_list<int>){3,4}}; , why do I have to writea.emplace_back((std::initializer_list<int>){5,6});? Shouldn't emplace_back have the same syntax rule as initialization?Anticipatory
Is this emplace back on an initializer list considered better than the move constructor?Anfractuosity
S
13

Template deduction cannot guess that your brace-enclosed initialization list should be a vector. You need to be explicit:

vec.emplace_back(std::vector<double>{0.,0.});

Note that this constructs a vector, and then moves it into the new element using std::vector's move copy constructor. So in this particular case it has no advantage over push_back(). @TimKuipers 's answer shows a way to get around this issue.

Simulate answered 3/7, 2014 at 10:18 Comment(5)
thanks, but i have another question to this: Why does it work for the second example? Since the compiler has similar information about the type (emplace_back expects to get arguments for a constructor of std::vector<double> and so is the second example)Persuade
Because in the second example you call the constructor of vector, which can handle the initializer list directly.Gus
So effectively you're not emplacing, but just pushing an already constructed element, rather than constructing the vector in place.Censure
@TimKuipers Yes, indeed. This would move-copy the argument into vec.Simulate
there's no point in using emplace if you're just gonna construct the object then emplace it. Tim Kuipers answer explainsGaud
S
4

There are really two issues here: the numeric type conversion and template argument deduction.

The std::vector<double> constructor is allowed to use the braced list (even of ints) for its std::vector<double>(std::initializer_list<double>) constructor. (Note, however, that std::initializer_list<int> does not implicitly convert to std::initializer_list<double>)

emplace_back() cannot construct the element from the brace expression because it is a template that uses perfect forwarding. The Standard forbids the compiler to deduce the type of {0,0}, and so std::vector<double>::emplace_back<std::initializer_list<double>>(std::initializer_list<double>) does not get compiled for emplace_back({}).

Other answers point out that emplace_back can be compiled for an argument of type std::initializer_list<vector::value_type>, but will not deduce this type directly from a {} expression.

As an alternative to casting the argument to emplace_back, you could construct the argument first. As pointed out in Meyers' Item 30 (Effective Modern C++), auto is allowed to deduce the type of a brace expression to std::initializer_list<T>, and perfect forwarding is allowed to deduce the type of an object whose type was deduced by auto.

std::vector<std::vector<double> > vec;
auto double_list = {0., 0.}; // int_list is type std::initializer_list<int>
vec.emplace_back(double_list); // instantiates vec.emplace_back<std::initializer_list<double>&>

emplace_back adds an element to vec by calling std::vector<double>(std::forward<std::initializer_list<double>>(double_list)), which triggers the std::vector<double>(std::initializer_list<double>) constructor.

Reference: Section 17.8.2.5 item 5.6 indicates that the this is a non-deduced context for the purposes of template argument deduction under 17.8.2.1 item 1.

Update: an earlier version of this answer erroneously implied that std::initializer_list<int> could be provided in place of std::initializer_list<double>.

Shurlocke answered 10/8, 2018 at 14:51 Comment(2)
Good point. But could you please point which clause in the Standard forbid the compiler to deduce the type of {0,0}?Curvy
@lw-cui Thanks for prompting. I don't have an "official" copy of the standard, but I noted relevant sections from the c++17 branch of the git repo. The most direct and concise text defines that "A function parameter for which the associated argument is an initializer list but the parameter does not have a type for which deduction from an initializer list is specified" is a non-deduced context, and so the emplace_back(Args&&) template cannot match.Shurlocke

© 2022 - 2024 — McMap. All rights reserved.