I defined the following
std::vector<std::pair<int,int> > my_vec;
my_vec.push_back( {1,2} ); //this works
my_vec.emplace_back( {1,2} ); // this doesn't work
std::pair<int,int> temp_pair = {1,2};
my_vec.emplace_back( temp_pair ); //this works
I am compiling with c++11. The third line is problematic, but I thought you can use emplace_back()
anywhere that you have push_back()
, but this is apparently wrong. Why does the third line not work?
emplace_back
takes a variadic parameter pack as argument:
template< class... Args > reference emplace_back( Args&&... args );
When you call it like this: emplace_back({1, 2})
you are calling it with one argument i.e. {1, 2}
and the Args
cannot be deduced. That is because of how the language has evolved. In C++ {1, 2}
doesn't have a type. It is a brace enclosed init-list and can be used in certain types of initializations, but all require the type of the initialized to be known. That is why temp_pair = {1,2};
works, because the type of temp_pair
is know and has a constructor matching (int, int)
.
Anyway emplace_back
wasn't supposed to be used like that, but like this instead:
my_vec.emplace_back(1, 2);
Also please note that even if these work:
my_vec.emplace_back(std::pair<int, int>{1, 2});
my_vec.emplace_back(temp_pair);
They shouldn't be used. They add no advantage over push_back. The whole point of emplace_back
is to avoid creating a temporary T
. The above calls all create the temporary std::pair<int, int>
.
but I thought you can use
emplace_back()
anywhere that you havepush_back()
For the most part that's correct. At least that was the intention. And you can indeed use it in your cese. You just have to adjust the syntax a little. So instead of push_back({1, 2})
you can use emplace_back(1, 2)
.
There is a situation where unfortunately you can't use emplace_back
: aggregates.
struct Agg
{
int a, b;
};
auto test()
{
Agg a{1, 2}; // ok, aggregate initialization
std::vector<Agg> v;
v.emplace_back(1, 2); // doesn't work :(
}
This doesn't work unless you add a constructor for Agg
. This is considered an open defect in the standard, but unfortunately they can't find a good solution to this. The problem is with how brace init initialization works and if you use it in generic code you can miss some constructors. For all the nitty-gritty details check this great post: Why can an aggreggate struct be brace-initialized, but not emplaced using the same list of arguments as in the brace initialization?
{1, 2}
is not an expressionThe syntax
{1, 2}
is very "strange" compared to other things in C++.
Normally in C++ you have an expression (e.g. x + 1.2
) and the expression has a deduced type... for example if x
is an int
variable the type of the expression will be double
because of implicit conversion int
→double
and how addition works.
Now back to {1, 2}
: this is "strange" because despite looking like an expression it's not... it's just syntax and its meaning will depend on where it's used.
In a sense typing here will work the opposite of most C++ places: normally in C++ it's "in"→"out" (the type "emerges" from the components) but here is "out"→"in" (the type is "injected" in the components).
The text {1, 2}
just doesn't mean enough by itself to be compiled (it can mean different things depending on where it is used).
All this boils down to the fact that {1, 2}
cannot be used exactly like an expression, even if the rules are carefully designed to trick you into thinking it does.
emplace_back
accepts constructor parametersemplace_back
was designed to be able to build the object directly inside the final place in the container... the expected parameters are parameters of a constructor and this is done to avoiding creating a temporary object just to be able make a copy for the final destination and then throwing it away.
The expected parameter of emplace_back
are therefore 1
and 2
... not a single thing because NOT building a temporary single thing is exactly the reason emplace_back
was designed for.
You can pass emplace_back
an instance because the contained type has a copy constructor and the instance is considered a parameter for the copy (move) constructor, not an object to be copied (moved) into destination (what push_back
expect). The operations performed in this case are the same, but the point of view is different.
To summarize: emplace_back
cannot use {1, 2}
because it can accept anything (so doesn't provide enough "context") and that syntax has not enough meaning. push_back
instead can accept it because it expects a specific type and this provides enough context for interpreting the syntax {1, 2}
.
This is a simplified explanation but, as usual, C++ went into a direction of even more parsing complexity and special cases, so I can understand why things are not clear for you.
The key point however is that emplace_back
was not meant to take a full object... for that use push_back
. The new emplace_back
construct should be used when you want to pass the CONSTRUCTOR PARAMETERS for building the final object in the container.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With