How do I use std::vector::emplace_back with vector<vector<int>>?

17k views Asked by At
vector<vector<int>> res;
res.emplace_back({1,2}); // change to res.push_back({1,2}); would work

This gives me the error:

main.cpp:61:25: error: no matching function for call to ‘std::vector<std::vector<int> >::emplace_back(<brace-enclosed initializer list>)’
main.cpp:61:25: note: candidate is:
In file included from /usr/include/c++/4.7/vector:70:0,
                 from /usr/include/c++/4.7/bits/random.h:34,
                 from /usr/include/c++/4.7/random:50,
                 from /usr/include/c++/4.7/bits/stl_algo.h:67,
                 from /usr/include/c++/4.7/algorithm:63,
                 from miscalgoc.hpp:1,
                 from main.cpp:1:
/usr/include/c++/4.7/bits/vector.tcc:92:7: note: void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {}; _Tp = std::vector<int>; _Alloc = std::allocator<std::vector<int> >]

How do I make this work? Also, why is an allocator needed here?

5

There are 5 answers

0
Mark Garcia On BEST ANSWER

The problem is that function template arguments doesn't deduce std::initializer_list from a braced-init-list (like { 1, 2 }).

Example:

#include <initializer_list>
#include <type_traits>


template<typename T>
void func(T arg) {
}


int main() {
    auto init_list = {1, 2};    // This works because of a special rule
    static_assert(std::is_same<decltype(init_list), std::initializer_list<int>>::value, "not same");

    func(std::initializer_list<int>{1, 2});     // Ok. Has explicit type.

    func({1, 2});   // This doesn't because there's no rule for function
                    //      template argument to deduce std::initializer_list
                    //      in this form.
}

Live example

std::vector::emplace_back() is a function template with its arguments being deduced. So passing it {1, 2} will not work because it couldn't deduce it. Putting an explicit type to it

res.emplace_back(std::initializer_list<int>{1,2});

would make it work.

Live example

1
Brian Gradin On

Take a look at the documentation for vector::emplace_back. emplace_back tries to create a new element in your vector, by calling the constructor for the new element with the arguments passed in. So basially, when you call emplace_back({1,2}), it tries to pass {1,2} in to a constructor, but since res is a vector of vectors of ints, it's looking at vector constructors, none of which can take a brace-enclosed initializer list.

Also, take a look at the documentation for vector::push_back. When push_back is called, it creates a default object (in this case, a vector of ints) and copies the values into it. I would guess that the reason that push_back({1,2}) works is that the brace-enclosed initializer list creates a value-type, which push_back accepts.

0
ashishk On

vector<vector > res;

res.emplace_back({1,2});

Default type detection and conversion happens only once. It does not work twice in it's use cases. For this to work you need two deductions.

  1. {1,2} -> deduce that the container is an initializer_list of Integers.

  2. res.emplace_back( any_initializer_list_of_ints ) -> here since the member element type is known to be vector of integers AND the initializer list of integers can be used to construct a vector of integers, so the initializer_list would need to be converted to vector.

The compilers don't deduce and convert it twice at the same spot. So this will never work.

0
tntnkn On

Though the question is well answered at the moment, I would like to elaborate on exactly why push_back works in this case.

From this cppreference page we see that

std::initializer_list object is automatically constructed when:

  1. a braced-init-list is used to list-initialize an object, where the corresponding constructor accepts an std::initializer_list parameter,
  2. a braced-init-list is used as the right operand of assignment or as a function call argument, and the corresponding assignment operator/function accepts an std::initializer_list parameter,
  3. a braced-init-list is bound to auto, including in a ranged for loop.
  1. is not our case.

bush_back per se does not accept initializer_list, but it accepts T. T in our case is std::vector<int>. So 2. also does not fit.

This list of ways to list initialize mentions this one, among others:

function ({ arg1, arg2, ... })

So, putting everything together, you call push_back that accepts std::vector<int> with braced-init-list. std::vector can be list-initialized with it, so the vector is constructed and passed to push_back (well, reference to it).

emplace_back does not work with braced-init-list as it is not accepting arguments of type T (std::vector<int> in our case).

You can try something like this:

#include <iostream>
#include <initializer_list>

struct V {
    int i;
    int j;

    V(int i, int j) {
        std::cout << "i j constructor\n";
    }

    V(std::initializer_list<int> il) {
        std::cout << "init list constructor\n";
    }
};

void
test_f(const V &v) {
    std::cout << "test_f called\n";
}

int main(void) {
    test_f( {1, 2, 3} );
    return 0;
}

The output would be:

init list constructor
test_f called
0
Jay Yang On

@Mark's answer is pretty correct. Now let's consider a more practical case. After some proper operations, you've collected some data with vector<int>, and feel like pushing it into vector<vector<int>>:

std::vector<std::vector<int>> res;

for (int i = 0; i < 10000; ++i) {
    //
    // do something
    //
    std::vector<int> v(10000, 0);  // data acquired
    res.push_back(v);
}

It's not like assigning values you already know. Utilizing std::initializer_list is probably no longer a solution. In such cases, you may use std::move (along with either emplace_back or push_back is acceptable)

for (int i = 0; i < 10000; ++i) {
    std::vector<int> v(10000, 0);  // will become empty afterward
    res.emplace_back(std::move(v));  // can be replaced by 
                                     // res.push_back(std::move(v));
}

The performance is more or less improved. You can still be benefited from the concept of xvalue move-insertion, constructing objects by move-constructor rather than copying.

UPDATE

The reason that res.push_back(move(v)) works is because they overload the method std::vector::push_back(value_type&& val) after C++11. It is made to support rvalue reference deliberately.