Почему `std:: pair <int, movable>` требует конструктор `const` `const [` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `}

Я занят тестированием реализации различных общих алгоритмов, и я использую типы с минимальной поддержкой предоставленных функций. Я столкнулся с этой странной установкой при использовании std::pair<T, movable> с типом T (например, int) и movable, определенным следующим образом:

struct movable
{
    movable() {}
    movable(movable&&) = default;
    // movable(movable const&) = delete;
    movable(movable&) = delete;
};

Идея имеет тип, который можно перемещать, но не копировать. Это отлично работает, например, с такими выражениями:

movable m1 = movable();
movable m2 = std::move(m1);

Однако при попытке использовать этот тип в качестве члена std::pair<...> он не работает! Чтобы получить код для компиляции, необходимо добавить конструктор копии delete d (!) С movable const& (или иметь только ту версию). Конструктор копирования, использующий ссылку не const, является недостаточным:

#include <utility>
auto f() -> std::pair<int, movable> {
    return std::pair<int, movable>(int(), movable());
}

Что здесь происходит? Является ли std::pair<...> превышенным, указав, что std::pair(std::pair const&) = default ed?

Проблема, по-видимому, сводится к спецификации конструктора копирования std::pair (в 20.3.2 [пар.пара]]:

 namespace std {
     template <class T1, class T2>
     struct pair {
         ...
         pair(const pair&) = default;
         ...
     };
 }

Быстрая проверка с моей реализацией подразумевает, что очевидная реализация, копирующая два элемента, не требует версии const& конструктора копирования movable. То есть, оскорбительная часть - это конструктор = default в pair!

Ответы

Ответ 1

Конструктор копирования

std::pair объявляется следующим образом:

pair(const pair&) = default;

Объявив этот конструктор копирования для movable:

movable(movable&) = delete;

вы запрещаете неявное создание movable(const movable&) (поэтому он даже не удаляется, просто нет такого конструктора), таким образом, это единственный созданный вами конструктор. Но конструктор std::pair copy требует, чтобы конструктор копирования его членов принимал const-ссылку, поэтому вы получаете ошибку компиляции.

Если вы добавите это:

movable(movable const&) = delete;

или (лучше) просто удалите объявление movable(movable&) = delete;, теперь у вас есть конструктор movable(movable const&), и поскольку он удален, конструктор копирования std::pair также удаляется.

Обновить. Рассмотрим более простой пример, демонстрирующий ту же проблему. Это не компилируется:

template <typename T>
struct holder {
    T t;
    // will compile if you comment the next line
    holder(holder const&) = default;
    // adding or removing move constructor changes nothing WRT compile errors
    // holder(holder&&) = default;
};

struct movable {
    movable() {}
    movable(movable&&) = default;
    // will also compile if you uncomment the next line
    //movable(movable const&) = delete;
    movable(movable&) = delete;
};

holder<movable> h{movable()};

Он будет компилироваться, если вы прокомментируете конструктор копирования holder, потому что это то, как работает неявное создание конструктора копии ([class.copy]/8:

Неявно объявленный конструктор копирования для класса X будет иметь вид

X::X(const X&)

если каждый потенциально сконструированный подобъект типа класса M (или его массив) имеет конструктор копирования, первый параметр которого имеет тип const M& или const volatile M&. В противном случае конструктор с неявным объявлением будет иметь вид

X::X(X&)

То есть, когда вы закомментируете объявление holder(holder const&) = default;, неявно объявленный конструктор копирования holder будет иметь форму holder(holder&). Но если вы этого не сделаете, конструктор T copy примет const T& (или const volatile T&), потому что это то, что будет вызываться в процедуре поэтапной копии, описанной в [class.copy]/15.

И если holder имеет конструктор перемещения, это еще проще - если вы закомментируете holder(holder const&) = default;, неявно объявленный конструктор копирования holder будет просто удален.