28

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?

Thanks for your help.

m47h
  • 1,611
  • 2
  • 18
  • 26

3 Answers3

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});
}
Tim Kuipers
  • 1,705
  • 2
  • 16
  • 26
  • It would actually call the move constructor, not the copy constructor. – jcai Jul 27 '17 at 21:04
  • Good catch. Edited my answer. – Tim Kuipers Jul 29 '17 at 00:33
  • 7
    Why do you use the old style C-cast syntax `(std::initializer_list){1,2}` here instead of the constructor syntax `std::initializer_list({1,2})`? – Peter Dec 21 '17 at 12:42
  • 3
    @Peter 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. – Tim Kuipers Dec 23 '17 at 16:24
  • 3
    I think the important question remains unanswered: If I can do `vector> a {{1,2}, {3,4}};` instead of `vector> a {(std::initializer_list){1,2}, (std::initializer_list){3,4}}; `, why do I have to write`a.emplace_back((std::initializer_list){5,6});`? Shouldn't emplace_back have the same syntax rule as initialization? – Archer Feb 23 '21 at 08:12
  • Is this emplace back on an initializer list considered better than the move constructor? – nickt Aug 13 '22 at 08:14
12

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.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • 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 and so is the second example) – m47h Jul 03 '14 at 10:22
  • Because in the second example you call the constructor of vector, which can handle the initializer list directly. – marli Oct 25 '16 at 20:09
  • 14
    So effectively you're not emplacing, but just pushing an already constructed element, rather than constructing the vector in place. – Tim Kuipers Nov 16 '16 at 12:04
  • 1
    @TimKuipers Yes, indeed. This would move-copy the argument into `vec`. – juanchopanza Apr 01 '18 at 08:20
  • 1
    there's no point in using emplace if you're just gonna construct the object then emplace it. Tim Kuipers answer explains – Puddle Jun 05 '19 at 01:12
3

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>.

MEI
  • 311
  • 2
  • 5
  • 1
    Good point. But could you please point which clause in the Standard forbid the compiler to deduce the type of `{0,0}`? – Lw Cui May 05 '20 at 22:56
  • @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. – MEI May 11 '20 at 14:09