Std:: map <>:: вставка с использованием не скопируемых объектов и равномерная инициализация

Посмотрите на следующий код:

#include <utility>
#include <map>

// non-copyable but movable
struct non_copyable {
    non_copyable() = default;

    non_copyable(non_copyable&&) = default;
    non_copyable& operator=(non_copyable&&) = default;

    // you shall not copy
    non_copyable(const non_copyable&) = delete;
    non_copyable& operator=(const non_copyable&) = delete;
};

int main() {
    std::map<int, non_copyable> map;
    //map.insert({ 1, non_copyable() });  < FAILS
    map.insert(std::make_pair(1, non_copyable()));
    // ^ same and works
}

Компиляция этого фрагмента завершается неудачей при раскомментировании отмеченной строки в g++ 4.7. Полученная ошибка указывает на невозможность копирования non_copyable, но я ожидал, что она будет перенесена.

Почему вставка std::pair, построенная с использованием единой инициализации, не выполняется, но не построена с использованием std::make_pair? Разве оба не должны производить rvalues, которые можно успешно перенести на карту?

Ответ 1

[Это полная переписывание. Мой предыдущий ответ не имел никакого отношения к проблеме.]

map имеет две релевантные перегрузки insert:

  • insert(const value_type& value) и

  • <template typename P> insert(P&& value).

Когда вы используете простой list-initializer map.insert({1, non_copyable()});, учитываются все возможные перегрузки. Но найден только первый (тот, который принимает const value_type&), так как другой не имеет смысла (нет способа догадаться, что вы хотели создать пару). Первая чрезмерная загрузка не работает, так как ваш элемент не копируется.

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

typedef std::map<int, non_copyable> map_type;

map_type m;
m.insert(map_type::value_type({1, non_copyable()}));

Теперь инициализатор списка знает, что искать конструкторы map_type::value_type, находит соответствующий mova & shy; ble one, а результат представляет собой пару rvalue, которая привязывается к P&& -перегрузке функции insert.

(Другой вариант - использовать emplace() с piecewise_construct и forward_as_tuple, хотя это будет намного более подробным.)

Я полагаю, что мораль здесь состоит в том, что инициализаторы списка ищут жизнеспособные перегрузки – но они должны знать, что искать!