Неожиданные копии с foreach над картой

Я пытаюсь перебрать записи карты, и я получаю неожиданные копии. Вот программа:

#include <iostream>
#include <map>
#include <string>

struct X
{
    X()
    {
        std::cout << "default constructor\n";
    }

    X(const X&)
    {
        std::cout << "copy constructor\n";
    }
};

int main()
{
    std::map<int, X> numbers = {{1, X()}, {2, X()}, {3, X()}};
    std::cout << "STARTING LOOP\n";
    for (const std::pair<int, X>& p : numbers)
    {
    }
    std::cout << "ENDING LOOP\n";
}

И вот вывод:

default constructor
copy constructor
default constructor
copy constructor
default constructor
copy constructor
copy constructor
copy constructor
copy constructor
STARTING LOOP
copy constructor
copy constructor
copy constructor
ENDING LOOP

Почему я получаю три копии внутри цикла? Копии исчезают, если я использую вывод типа:

for (auto&& p : numbers)
{
}

Что здесь происходит?

Ответ 1

Тип значения map<K,V> - pair<const K,V>; поэтому вашему циклу необходимо преобразовать pair<const int,X> в pair<int,X>, скопировав как ключ, так и значение, чтобы дать вам ссылку на этот тип.

Использование правильного типа (указанного явно или выведенного с помощью auto) удалит копии.

Ответ 2

Копии связаны с тем, что вы выполняете итерацию карты, но привязываете неправильный ссылочный тип. value_type для этой карты std::pair<const int, X> (обратите внимание на const). Поскольку они не являются одним и тем же типом, компилятор создает временную привязку и привязывает ссылку.

В большинстве случаев вы можете (вероятно, должен) итератор использовать auto& или const auto&, что позволит избежать подобных проблем. Если вы хотите указать тип, вы можете использовать вложенный value_type или точный тип:

for (const std::map<int,X>::value_type& r : numbers) {
// or
for (const std::pair<const int,X>& r : numbers) {

Предпочтение должно быть: const auto& r then const std::map<...>::value_type&, а затем const std::pair<const int, X>&. Обратите внимание, что дальше вправо в списке, чем больше знаний вы предоставляете, и тем меньше вы позволяете компилятору помочь вам.

Ответ 3

value_type не std::pair<K, V>, а std::pair<K const, V>. То есть вы не можете изменить ключевой компонент элементов. Если компилятор находит запрос на получение std::pair<K, V> const& из std::pair<K const, K> const&, он использует конструктор преобразования std::pair<...> и создает подходящий временный.

Вместо этого рекомендуется использовать value_type, чтобы избежать различий:

for (std::map<int, X>::value_type const& p: numbers)
    ...

В качестве альтернативы вы можете выбрать тип:

for (auto const& p: number)
    ...