1

I have got std::vector<std::map<int, std::unique_ptr<int>>> container (if to simplify). Initially, I have to insert in std::vector some amount of std::map, each of them will have one key-value pair. I tried such code:

#include <iostream>
#include <map>
#include <vector>
#include <memory>

using namespace std;

int main()
{
    vector<map<int, std::unique_ptr<int>>> data{};

    for (int x = 0; x < 10; x++)
    {
        data.emplace_back(std::make_pair(x, std::make_unique<int>(x)));
    }
}

But it did not work. How can I change it to make it work as expected?

EDIT: I understood my mistake, I had to use this piece of code to add th element:

std::map<int, std::unique_ptr<int>> m;
m.emplace(x, std::make_unique<int>(y));
data.emplace_back(std::move(m));

Then, the code will work in online compilers, but it still does not work in Visual Studio.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Roman Leshchuk
  • 192
  • 1
  • 7
  • You need to feed a `std::map>` into `data.emplace_back(...)`, not a pair of `int` and `unique_ptr` – chrysante Jun 14 '23 at 12:32
  • Also https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice – chrysante Jun 14 '23 at 12:34
  • 1
    There's a reason that highly nested monolithic data structures are discouraged. They lead to code that is hard to write and hard to understand. Define a few simple classes to break down the data structure into named entities. It will make life easier for everyone including yourself. – john Jun 14 '23 at 12:36
  • I know, I do not use `using namespace std;` in my project, it is minimal reproducible example. Also I tried to insert like that: `data.emplace_back(map>{ std::make_pair(x, std::make_unique(x)) });` but it did not work – Roman Leshchuk Jun 14 '23 at 12:36
  • The problem is constructing the map, without copying the value type, which because of the unique_ptr is uncopyable. Much as you clearly like writing minimal code that does a great deal with very little, in this case I think you are just going to have to break the code down into smaller steps. – john Jun 14 '23 at 12:53
  • "Initially, I have to insert in std::vector some amount of std::map" - In your own words: where, in your program, is there code that creates any `std::map` at all? What `std::map` do you think should get inserted into the vector, and why? Can you show code that would create such a `std::map`? Where the code says `std::make_pair(x, std::make_unique(x))`, what kind of thing do you think is created by that code? Does it make sense to store that in a map? What if you tried having a map, and using something like that to put data into the map, then putting the map into the vector? – Karl Knechtel Jun 14 '23 at 13:02
  • "But it did not work. How can I change it to make it work as expected?" This is not a clear question, because you do not describe what happened, and you do not describe what should happen instead. It is also not a useful question, because what is preventing you from solving it isn't a lack of knowledge or understanding, but a lack of attention and analysis. – Karl Knechtel Jun 14 '23 at 13:03

1 Answers1

6

The cause of your error is that emplace_back will attempt to construct a map in the vector like:

std::map<int, std::unique_ptr<int>>(std::make_pair(...))

See also: std::map constructors

However, this is not possible, because the constructor which takes pairs is taking std::initializer_list<std::pair<K, V>>. There is no constructor that takes a single pair. The correct way is:

map<int, unique_ptr>{std::make_pair(...)}

However, constructors taking std::initializer_list<T> require T to be copyable, and std::unique_ptr<int> is not copyable, so the containing T = std::pair<int, std::unique_ptr<int>> also isn't copyable. This leaves us with the following solution:

#include <iostream>
#include <map>
#include <vector>
#include <memory>

int main()
{
    using map_type = std::map<int, std::unique_ptr<int>>;

    std::vector<map_type> data{};

    for (int x = 0; x < 10; x++)
    {
        for (int y = 0; y < 10; y++)
        {
            map_type m;
            m.emplace(x, std::make_unique<int>(y));
            data.emplace_back(std::move(m));
        }
    }
}

See live example

Visual Studio Issues

In MSVC's standard library, it is impossible to use a std::vector<std::map<int, std::unique_ptr<int>>. The reason is that due to strong exception guarantees, std::vector requires its element type to be std::nothrow_move_constructible, or copyable.

You can see that in the MSVC STL, std::map has non-noexcept move constructor:

map(map&& _Right) : _Mybase(_STD move(_Right)) {}

Here is a quick and dirty fix for this issue:

template <typename K, typename V>
struct nothrow_map : std::map<K, V> {
    nothrow_map() = default;
    nothrow_map(nothrow_map&& other) noexcept : std::map<K, V>(std::move(other)) {}
};

// ...

using map_type = nothrow_map<int, std::unique_ptr<int>>;

See also: std::move_if_noexcept (used by std::vector internally)

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
  • @JanSchultke Nice work, if I could give this answer more than one vote I would. – john Jun 14 '23 at 13:10
  • 1
    @RomanLeshchuk the answer now includes a dirty fix. It works by inheriting from `std::map` and giving the derived type `nothrow_map` a `noexcept` move constructor. It's dirty, but unlikely to ever fail, since `std::map`'s move constructor will only throw on allocation failure in this case, and that's unlikely. – Jan Schultke Jun 14 '23 at 13:10