Search code examples
c++c++11stlpushemplace

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?


Solution

  • 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 before C++20.

    struct Agg
    {
        int a, b;
    };
    
    auto test()
    {
        Agg a{1, 2}; // ok, aggregate initialization
    
        std::vector<Agg> v;
        v.emplace_back(1, 2); // works only since C++20
    }
    

    This doesn't work before C++20 unless you add a constructor for Agg. This was 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?