Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

emplace_back not working with std::vector<std::map<int, int>>

Tags:

c++

c++11

I am trying to do emplace_back into a std::vector<std::map<int, int>>, but could not find the right syntax to do it.

#include<map>
#include<vector>

int main()
{
    std::vector<std::map<int, int>> v;
    std::map<int,int> a {{1,2}};

    v.push_back({{1,2}});

    v.emplace_back({1,2});    // error
    v.emplace_back({{1,2}});  // error
    v.emplace_back(({1,2}));  // error
}

push_back works here, but not emplace_back. How can I get emplace_back working?

like image 520
jha-G Avatar asked Oct 19 '15 05:10

jha-G


2 Answers

emplace_back does forward all arguments to a matching constructor of the member type. Now, std::map has a initializer-list constructor, but it expects a list of std::pair<const Key, Value>, i.e. std::pair<const int, int>. push_back is not a template, so it just expects one type and thus performs the conversion in place. That is, no type-deduction occurs here.

You would need to explicitly state that you want to have a std::pair; the following should work:

#include<map>
#include<vector>

int main()
{
    std::vector<std::map<int, int>> v;

    v.emplace_back(std::initializer_list<std::pair<const int, int>>{
            {1,2},{3,4},{5,6}});

    return 0;
}

For the same reason, this does not compile:

    v.emplace_back({std::pair<const int,int>(1,2),
                    std::pair<const int,int>(3,4)});

This is because, though a brace-enclosed list may yield an initializer-list, it doesn't have to. It can also be a constructor call or something like that. So, writing

auto l = {std::pair<const int,int>(1,2),
          std::pair<const int,int>(3,4)};

yields an initializer list for l, but the expression itself might be used in another way:

std::pair<std::pair<const int, int>, std::pair<const int, int>> p =
          {std::pair<const int,int>(1,2),
          std::pair<const int,int>(3,4)}

This whole stuff gets a bit messy.

Basically, if you have an brace-enclosed-list, it may yield an initializer list or call a matching constructor. There are cases where the compiler is not able to determine which types are needed; emplace_back is one of them (because of forwarding). In other cases it does work, because all types are defined in the expression. E.g.:

#include <vector>
#include <utility>

int main() 
{
    std::vector<std::pair<const int, int>> v = 
         {{1,2},{3,4},{5,6}};
    return 0;
}

Now the reason it doesn't work is that no type can be deduced. I.e. emplace_back tries to deduce the name of the input types, but this is not possible, since a brace-enclosed-list has several types it can describe. Hence there is not a matching function call.

like image 193
Klemens Morgenstern Avatar answered Nov 01 '22 11:11

Klemens Morgenstern


One can achieve that using a helper function as follows:

 #include <map>
 #include <vector>

 void emplace_work_around(
    std::vector<std::map<int, int>>& v,
    std::initializer_list<std::pair<const int,int>> && item
 )
 {
    v.emplace_back(std::forward<std::initializer_list<std::pair<const int,int>>>(item));
 }

int main()
{
    std::vector<std::map<int, int>> v;

    emplace_work_around(v,{{1,2}});
}

The problem was when we write:

v.emplace_back({{1,2}});  // here {{1,2}} does not have a type.

the compiler is not able to deduce the type of the argument, and it can't decide which constructor to call.

The underlying idea is that when you write a function like

template<typename T>
void f(T) {}

and use it like

f( {1,2,3,4} ); //error

you will get compiler error, as {1,2,3,4} does have a type.

But if you define your function as

template<typename T>
void f(std::initializer_list<T>) {}
 f( {1,2,3,4} );

then it compiles perfectly.

like image 43
jha-G Avatar answered Nov 01 '22 11:11

jha-G