Search code examples
c++c++11movestdmap

How to move elements from std::map to std::vector


I have a std::map which contains a bunch of key value pairs. I want move these elements into a std::vector. I tried using std::transform from <algorithm> to do this, but it doesn't work. Here's my code:

#include <algorithm>
#include <cstdint>
#include <iostream>
#include <map>
#include <vector>

struct Values
{
        uint32_t a = 0;
        uint32_t b = 0;

        Values(uint32_t x, uint32_t y): a(x), b(y) {}
};

template <typename MapType>
auto foo(MapType&& map)
{
        std::vector<typename MapType::value_type> v;
        v.reserve(map.size());

        std::transform(map.begin(), map.end(), std::back_inserter(v), [](const auto& kv) -> decltype(auto) { return std::move(kv); });
        std::cout << map.size() << " " << v.size() << "\n";
}

int main()
{
        std::map<uint32_t, Values> data;
        for(uint32_t i = 0; i < 100; i++)
                data.emplace(std::piecewise_construct, std::forward_as_tuple(i), std::forward_as_tuple(2*i, 3*i));

        foo(std::move(data));

        return 0;
}

I'm compiling this code using gcc:

g++ -std=c++23 -Ofast -Wall -Wextra -Wpedantic -Wconversion -Werror main.cpp

Here's the link to compiler explorer.

The program outputs the number of elements in the std::map and std::vector after the call to std::transform. The output that I get is:

100 100

This tells me that instead of the elements being moved, they we just copied into the vector. How can I change this code to use move instead.


Solution

  • "moving" in C++ doesn't remove the elements, it merely uses the move constructor when creating new objects using them, which for trivial types will just copy them, but for objects that hold resources like unique_ptr or vector the move constructor will correctly steal the managed resources, you can declare a move constructor to see it being called.

    lastly, you cannot move const objects (see move constructor declaration) so const auto& kv needs to be changed to auto& kv in your transform, then you need to clear the map of "moved from" objects.

    #include <algorithm>
    #include <cstdint>
    #include <iostream>
    #include <map>
    #include <vector>
    
    struct Values
    {
        uint32_t a = 0;
        uint32_t b = 0;
        Values(const Values&) { std::cout << "copied!\n"; }
        Values(Values&&) { std::cout << "moved!\n"; }
        Values(uint32_t x, uint32_t y) : a(x), b(y) {}
    };
    
    template <typename MapType>
    auto foo(MapType&& map)
    {
        std::vector<typename MapType::value_type> v;
        v.reserve(map.size());
    
        std::transform(map.begin(), map.end(), std::back_inserter(v), [](auto& kv) -> decltype(auto) { return std::move(kv); });
        map.clear(); // clear map content which has been "moved from"
        std::cout << map.size() << " " << v.size() << "\n";
    }
    
    int main()
    {
        std::map<uint32_t, Values> data;
        for (uint32_t i = 0; i < 100; i++)
            data.emplace(std::piecewise_construct, std::forward_as_tuple(i), std::forward_as_tuple(2 * i, 3 * i));
    
        foo(std::move(data));
    
        return 0;
    }
    

    you will see a 100 moved printed with auto& and a 100 copied printed with const auto&