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.
"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&