Reputation: 393
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?
Upvotes: 1
Views: 859
Reputation: 1300
Dereferencing the iterator for std::unordered_map
returns references. And then, structured bindings with auto&&
point to a reference, so no copies are made.
One tool I like to use for this kind of thing is C++ Insights. For this example, we can see how all variables are references and there are no copies are instantiated in the loop:
int main()
{
std::unordered_map<std::string, std::unique_ptr<int> > map = ...;
{
std::unordered_map<std::basic_string<char, std::char_traits<char>, ...> & __range1 = map;
std::__detail::_Node_iterator<...> __begin1 = __range1.begin();
std::__detail::_Node_iterator<...> __end1 = __range1.end();
for(; std::__detail::operator!=(__begin1, __end1); __begin1.operator++())
{
std::pair<const std::basic_string<...>, std::unique_ptr<int, ...> & __operator11 = __begin1.operator*();
const std::basic_string<...>& _ = std::get<0UL>(__operator11);
std::unique_ptr<int, ...>& v = std::get<1UL>(__operator11);
func(std::unique_ptr<int, std::default_delete<int> >(std::move(v)));
}
}
}
Upvotes: 2
Reputation: 119552
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.
Upvotes: 4