Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vector<map<move-only type>> does not compile with MSVC

Making a vector of a map of move-only types doesn't seem to work properly on Windows. See code here: https://godbolt.org/z/yAHmzh

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

// vector<vector<move-only>> works
void foo() {
    std::vector<std::vector<std::unique_ptr<int>>> outer;
    std::vector<std::unique_ptr<int>> inner;
    std::unique_ptr<int> p = std::make_unique<int>(1);
    inner.push_back(std::move(p));
    outer.push_back(std::move(inner));
}

// vector<map<move-only>> fails to compile upon inserting an element.
void bar() {
    std::vector<std::map<std::unique_ptr<int>, std::unique_ptr<int>>> vec;
    std::map<std::unique_ptr<int>, std::unique_ptr<int>> map;
    std::unique_ptr<int> p1 = std::make_unique<int>(1);
    std::unique_ptr<int> p2 = std::make_unique<int>(2);

    map.insert(std::make_pair(std::move(p1), std::move(p2)));

    // The following line fails to compile on windows. It errors with a message about
    // the unique_ptr copy constructor being explicitly deleted. This seems to only happen
    // on windows. GCC and clang have no problem with this.
    vec.push_back(std::move(map));
}

int main(int argv, char** argc)
{
    foo();

    bar();
}

GCC and Clang have no problem with that code, but MSVC fails to compile.

Looking for a workaround to let me do this that will compile on all major compilers.

like image 984
stbrody Avatar asked Mar 03 '23 12:03

stbrody


2 Answers

To enforce move semantics for vector, we need to inform C++ (specifically std::vector) that the move constructor and destructor does not throw, using noexcept. Then the move constructor will be called when the vector grows. See this note:

To make the strong exception guarantee possible, user-defined move constructors should not throw exceptions. For example, std::vector relies on std::move_if_noexcept to choose between move and copy when the elements need to be relocated.

For more about what's said in the standard, read C++ Move semantics and Exceptions

If the constructor is not noexcept, std::vector can't use it, since then it can't ensure the exception guarantees demanded by the standard.

In case of std::map, the standard doesn't say anything about exception safety for move constructor of the map. So, compilers (in your case, gcc and clang) can mark functions as noexcept irrelevant of whether the Standard mandates it or not.

For alternatives or workaround, see my example below (tested with gcc):

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

void foo(void) 
{
    std::vector<std::vector<std::unique_ptr<int>>> outer;
    std::vector<std::unique_ptr<int>> inner;
    std::unique_ptr<int> p = std::make_unique<int>(1);
    inner.emplace_back(std::move(p));
    outer.emplace_back(std::move(inner));
}

void bar(void) 
{
    std::vector<std::pair<std::unique_ptr<int>, std::unique_ptr<int>>> vec;
    std::unique_ptr<int> p1 = std::make_unique<int>(1);
    std::unique_ptr<int> p2 = std::make_unique<int>(2);

    auto pair = std::make_pair(std::move(p1), std::move(p2));

    vec.emplace_back(std::move(pair));
}

void bar2(void) 
{
    std::vector<std::unique_ptr<std::map<std::unique_ptr<int>, std::unique_ptr<int>>>> vec;
    std::unique_ptr<int> p1 = std::make_unique<int>(1);
    std::unique_ptr<int> p2 = std::make_unique<int>(2);

    auto map = std::make_unique<std::map<std::unique_ptr<int>, std::unique_ptr<int>>>();
    map->emplace(std::move(p1), std::move(p2));

    vec.emplace_back(std::move(map));
}

int main(int argc, char *argv[])
{
    foo();
    bar();
    return 0;
}

BONUS:

Use emplace_back when possible. It can be faster (but often is not), it can be clearer and more compact, but there are also some pitfalls (especially with non-explicit constructors).

like image 194
abhiarora Avatar answered Apr 09 '23 22:04

abhiarora


The move constructor of std::map is not defined to be noexcept by the Standard. Therefore std::vector falls back to using the copy constructor (e.g. by the use of std::move_if_noexcept).

That said, compilers are allowed to mark functions as noexcept irrelevant of whether the Standard mandates it or not. This is probably what GCC and Clang do (the libraries that they use).

You'll notice the same situation applies for std::vector<std::list<std::unique_ptr<int>>> (and perhaps other).

like image 32
DeiDei Avatar answered Apr 09 '23 22:04

DeiDei