I have a map like this
std::unordered_map<std::string, std::unique_ptr<V>> map;
I want to eventually run a function on all the remaining V
in the map, so I do the following:
for (auto&& [_, v] : map) {
func(std::move(v));
}
This works, but I've been naively assuming the compiler would ignore the key values, since they aren't used in the for loop. Now I'm thinking the iterator is yielding const std::string
as the first argument, so really there's a lot of unnecessary string building going on. What exactly happens here (regarding the key values)? Is there a way to avoid any string copying?
In the C++ standard, [stmt.ranged] specifies that a range-based for loop has equivalent semantics to a certain ordinary for loop. In your case, this is:
{
auto &&__range = map ;
auto __begin = __range.begin() ;
auto __end = __range.end() ;
for ( ; __begin != __end; ++__begin ) {
auto&& [_, v] = *__begin;
func(std::move(v));
}
}
(Notice that the expression or braced-init-list on the right hand side of :
becomes the initializer for auto &&__range
, and the declarator or structured binding on the left hand side of :
is initialized with *__begin
. This is how the general transformation works.)
In this full form, we can see exactly what is happening. Every time the iterator is dereferenced, it just yields a reference to the (string
, unique_ptr
) pair that is stored inside the map. Then the structured binding declaration makes _
an lvalue referring to that string
, and v
an lvalue referring to that unique_ptr
. Finally, std::move
converts the lvalue into an rvalue and that rvalue is passed into func
. No string copy is made.