Reputation: 833
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?
Upvotes: 29
Views: 19032
Reputation: 133
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:
- a braced-init-list is used to list-initialize an object, where the corresponding constructor accepts an std::initializer_list parameter,
- 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,
- a braced-init-list is bound to auto, including in a ranged for loop.
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
Upvotes: 1
Reputation: 137
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,2} -> deduce that the container is an initializer_list of Integers.
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.
Upvotes: -1
Reputation: 413
@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.
Upvotes: 6
Reputation: 17708
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.
}
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.
Upvotes: 51
Reputation: 2275
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.
Upvotes: 4