25

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?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
g-217
  • 2,069
  • 18
  • 33

3 Answers3

13

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.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
  • Not really, it actually makes sense. It only get's a bit complicated with forwarding, but you can always write a standard proposal, to fix this problem. That would be much more productive... – Klemens Morgenstern Oct 19 '15 at 10:10
  • You obviously don't understand the type-system of C++. There could be a solution to the problem, that brace-enclosed-lists cannot bind to a templated function parameter (which again: makes sense, because it ain't no type), but someone would need to write it. The trouble is, that in C++ the type-resolution has to occur at compiler-time and that is why it doesn't work. That's not stupid, because it's a design choice to make the language fast. – Klemens Morgenstern Oct 19 '15 at 11:24
  • lol right okay I don't understand the type system of C++. Is it a common habit of yours to take someone's opinion of the _expressiveness_ of a language, and turn it into a rancid, completely uninformed bashing of that person's understanding of how the language is technically constructed? How sad for you. I suggest having a look at my profile before continuing your little trip down hatred lane. – Lightness Races in Orbit Oct 19 '15 at 11:27
  • Expressiveness is also a very subjective criteria, and that's what you said secondly after calling it stupid. I'm just saying, having an expression, which does not necessarily yield one type, leads to such problems, when passing it to functions. Either one solves that by a new expression for templates or you get this sort of problems. Another possibility is to always convert it to an std::initializer_list, but that would cost a lost of performance. So I don't get what you mean be expressiveness except for "I don't like it". – Klemens Morgenstern Oct 19 '15 at 11:45
  • Yes, it's subjective. And, subjectively, I think it's stupid. Can I help you? – Lightness Races in Orbit Oct 19 '15 at 11:46
  • Yes you can, help to improve it. – Klemens Morgenstern Oct 19 '15 at 11:48
  • No. I already addressed and rejected that idea. Besides the fact that I _have_ contributed to the standard several times, the only way to really fix the abject mess that is C++ is to replace it, which has already been done. – Lightness Races in Orbit Oct 19 '15 at 11:59
  • @LightnessRacesinOrbit: Which of the aspirants are you referring to? – Deduplicator Oct 19 '15 at 14:19
  • @Deduplicator: I leave that unspecified! – Lightness Races in Orbit Oct 19 '15 at 14:22
  • @LightnessRacesinOrbit: Thanks for the laugh. Sure you aren't still in C++? – Deduplicator Oct 19 '15 at 14:23
7

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.

g-217
  • 2,069
  • 18
  • 33
0

This seems to be currently unsupported, if I understand these issue reports right:

http://cplusplus.github.io/LWG/lwg-active.html#2089

http://cplusplus.github.io/LWG/lwg-active.html#2070

galinette
  • 8,896
  • 2
  • 36
  • 87