7

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?

max66
  • 65,235
  • 10
  • 71
  • 111
24n8
  • 1,898
  • 1
  • 12
  • 25

2 Answers2

11

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?

Heisenbug
  • 38,762
  • 28
  • 132
  • 190
bolov
  • 72,283
  • 15
  • 145
  • 224
5

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.

Community
  • 1
  • 1
6502
  • 112,025
  • 15
  • 165
  • 265