Друг дал мне ответ, и я делюсь им.
Ответ заключается в концепции неявного преобразования (копирования), которое разрешено делать компилятору.
Давайте посмотрим на следующее простой пример:
const char x = 4;
const char& r = x; // r is reference for x. Checking their addresses yields the same
// address.
const int& ir = c; // implicit creation of object + conversion(copy).
// ir is different type of x, therefor compiler does implicit
// conversion(copy): it creates behind the scene an object of int,
// convert x into this temporary object. The temporary int object is
// then bound to the reference ir. Checking addresses of ir and x
// yields different addresses because ir is reference of the temporary
// object, not to x
Так что даже если мы используем планирование ссылок для простого наведения на существующий объект, мы можем фактически получить конструкцию объекта + копию (если типы различаются и между ними есть преобразование).
То же самое происходит в l oop, который я дал в вопросе:
std::unordered_map<uint32_t, std::shared_ptr<char>> map;
for (const std::pair<uint32_t, std::shared_ptr<char>>& item : map)
{
// do something
}
, тогда как реальный объект, удерживаемый картой, имеет тип
std::pair<const uint32_t, std::shared_ptr<char>>
l oop использует ссылку другого типа:
std::pair<uint32_t, std::shared_ptr<char>>
и, следовательно, за кулисами для каждой итерации создается неявно новый временный объект и выполняется операция копирования для преобразования.
Мало того, что это неэффективно, но также не удалось его скомпилировать, когда значение - unique_ptr, потому что преобразование копирует и unique_ptr не может быть скопировано.
Вот почему использование 'auto' может спасти вас от таких ошибок,
std::unordered_map<uint32_t, std::shared_ptr<char>> map;
for (const auto& item : map)
{
// do something
}
Я сам иногда предпочитаю играть и использовать явную форму вместо 'auto', чтобы столкнуться с такими проблемами и научиться :)