Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

emplace_back() vs push_back when inserting a pair into std::vector

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?

like image 760
24n8 Avatar asked Dec 23 '18 02:12

24n8


2 Answers

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 have push_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?

like image 154
bolov Avatar answered Oct 03 '22 07:10

bolov


1) {1, 2} is not an expression

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

2) emplace_back accepts constructor parameters

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

Consequences

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.

like image 34
6502 Avatar answered Oct 03 '22 08:10

6502