Почему тип CopyConstructible также должен быть MoveConstructible?

Как указано в cppreference, требование для типа T быть CopyConstructible означает, что он также будет MoveConstructible.

Черновик концепции STL CopyConstructible содержит:

template <class T>
concept CopyConstructible =
   std::MoveConstructible<T> &&
   std::Constructible<T, T&> && std::ConvertibleTo<T&, T> &&
   std::Constructible<T, const T&> && std::ConvertibleTo<const T&, T> &&
   std::Constructible<T, const T> && std::ConvertibleTo<const T, T>;

который поддерживает именованный оператор требования. Учитывая приведенное выше определение, тип вроде:

struct HaveCopy {
   HaveCopy(const HaveCopy&)  = default;
   HaveCopy(HaveCopy&&)       = delete;
   HaveCopy& operator= (const HaveCopy&)  = default;
   HaveCopy& operator= (HaveCopy&&)       = delete;
};

не проходит простой тест:

static_assert(std::CopyConstructible<HaveCopy>);

тогда как он передает старый:

static_assert(std::is_copy_constructible<HaveCopy>::value);

Итак, вопрос: почему? Каковы намерения комитета по стандартам в этом вопросе? HaveCopy не может быть перестроен, но, на мой взгляд, в значительной степени копируем, и std::is_copy_constructible<> согласен со мной.

Такое же поведение наследуется концепцией Copyable, а именно:

template <class T>
concept Copyable =
   std::CopyConstructible<T> &&
   std::Movable<T> &&
   std::Assignable<T&, const T&>;

Итак тест:

static_assert(std::Copyable<HaveCopy>);

тоже не получится. На этот раз провал удваивается. И CopyConstrucible<>, и Movable<> не согласны с тем, что HaveCopy можно копировать.

Обсуждение здесь чем-то похоже, но не отвечает на вопрос почему< /сильный>. Зачем нам нужно такое поведение? Исключает ли этот тип проверки действительные конструируемые типы копирования или HaveCopy вообще не конструирует копирование? Последнее кажется мне действительно странным, если это правда.

Какие-нибудь мысли?


person hoo2    schedule 06.02.2019    source источник
comment
Разве ответ Джонатана Уэйкли в связанной ветке также не отвечает на вопрос «почему»? MoveConstructible не требует, чтобы что-либо перемещалось, возможна только конструкция из rvalue, и все типы CopyConstructible могут быть созданы из rvalue (даже если они могут выполнять глубокое копирование, а не перемещение).   -  person lubgr    schedule 06.02.2019
comment
насколько я понимаю, когда что-то перемещается, вас не должно волновать, действительно ли оно было перемещено или скопировано, поэтому, если что-то CopyConstructible, оно автоматически становится MoveConstructible, вы не можете заметить разницу, и на самом деле это не дополнительное требование.   -  person 463035818_is_not_a_number    schedule 06.02.2019
comment
я согласен с lubr и пометил бы этот вопрос как дубликат, на какую часть, по вашему мнению, не ответил связанный ответ?   -  person 463035818_is_not_a_number    schedule 06.02.2019
comment
Хотя вполне возможно, что я не очень хорошо это понимаю... В ответе Уэйкли говорится, что мы стараемся избегать извращенных типов [и/так], «нам нужен конструктор перемещения также потому, что кто-то может использовать его для копирования». Почему мы делаем это, запрашивая концепцию MoveConstructible? И почему при этом мы исключаем HaveCopy из числа CopyConstructible?   -  person hoo2    schedule 06.02.2019
comment
@ user463035818 Мой вопрос о том, почему. Я не могу понять, почему, пытаясь избежать порочных типов, мы запрещаем типы с удаленным конструктором перемещения, но с вполне допустимым конструктором копирования. Зачем нам такое поведение? Исключает ли этот тип проверки действительные типы, конструируемые для копирования, или HaveCopy вообще не является конструируемым для копирования?   -  person hoo2    schedule 06.02.2019
comment
Это как спросить, почему все числа, делящиеся на 4, должны делиться на 2? Любой T, который может быть создан из const T &, может быть создан из T && из-за неявного преобразования T && в const T &. может быть создан из T && так определяется MoveConstructible   -  person Caleth    schedule 06.02.2019
comment
@Caleth Как показал OP с HaveCopy, это не обязательно так. Это правда, что конструктор копирования был бы кандидатом, но неверно, что конструктор копирования обязательно является лучшим кандидатом.   -  person Barry    schedule 06.02.2019


Ответы (1)


Да, CopyConstructible концепция совершенно иная, чем черта типа std::is_copy_constructible. Вы сосредоточены на конструкторе перемещения, но есть и много других случаев, которые следует учитывать. Как вы думаете, этот тип должен быть CopyConstructible?

struct A {
    A(A&) = delete;
    A(A const&);
};

Как насчет этого?

struct B {
    explicit B(B const&);
};

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

Концепции — это не только проверка синтаксиса, но и обеспечение соблюдения семантических требований для создания значимых типов, которые в конечном итоге легче кодировать. Если вы просто отметите is_copy_constructible, все, что вы позволите себе сделать, это явно построить свой тип из константного lvalue. Написание T x = y;, даже если y является const T, уже выходит за рамки этой области! Это может быть буквально то, что означает возможность создания копии, но это гораздо менее значимо, чем более широкое «Я могу построить T из T», которое намного ближе к тому, о чем мы думаем, когда рассматриваем копирование. Вот что дает нам концепция CopyConstructible.

По мере того, как вы проходите через библиотеку, появляются другие концепции, которые требуют большего (синтаксически и семантически), чем может предложить прямой перевод их названия. EqualityComparableWith<T,U> не только проверяет, могу ли я написать t == u, но и u == t, t != u , а также u != t. StrictTotallyOrdered не только проверяет операторы упорядочивания, но и также проверяет ==. Важно иметь единое целое.

person Barry    schedule 06.02.2019
comment
Значит, все дело в формулировках? Если да, то обратите внимание на мою интуитивную реакцию на формулировку концепции. Сначала процитирую ваш (очень хороший) ответ для CopyConstructible: я могу построить T из T. Это то, для чего предназначен Constructible? Во-вторых... было бы лучше, если бы для CopyConstructible было "Я могу КОПИРОВАТЬ T из T"? - person hoo2; 06.02.2019
comment
@hoo2: Подобные требования больше касаются жизнеспособности выражений, чем семантических эффектов (хотя они у них тоже есть). Рассмотрим T t = some_t; и T t = func_returning_t();. Заботится ли пользователь о том, является ли это КОПИЕЙ или нет? Или их просто волнует, что у них есть T и они могут построить из него еще один T? - person Nicol Bolas; 06.02.2019
comment
@Nicol Bolas: Конечно, пользователю не все равно. Рассмотрим библиотеку с объектами, содержащими аппаратные ресурсы. Эти объекты разрешено перемещать, но не копировать. Автору этой библиотеки приходится делать много проверок, и более правильная формулировка могла бы облегчить его жизнь. На мой взгляд, каждый раз, когда мы делаем подобные предположения, мы ослабляем язык. Разве точный инструмент не лучший инструмент? Говоря это, я не имею в виду, что имею право на формулировку. Я говорю это в рамках обсуждения. - person hoo2; 06.02.2019
comment
@hoo2: Да, пользователю важно, является ли тип только перемещением, и поэтому T t = some_t; не компилируется. Но почему пользователь должен желать, чтобы T t = func_returning_t(); не компилировалось? Если вы можете создать экземпляр из lvalue, почему вы не можете создать его из rvalue? Тип, который можно копировать, но нельзя перемещать, — это тип, который нарушает базовые концепции этих операций. - person Nicol Bolas; 06.02.2019
comment
@hoo2 разрешено перемещать, но не копировать - вполне разумная семантика, в которой нет недостатка в важных вариантах использования. разрешено копировать, но не перемещать... буквально возможно написать. Они не совсем в равных условиях. - person Barry; 06.02.2019
comment
В соответствующей заметке, согласно cppreference, многие контейнеры (например, вектор) заявляют, что T must meet the requirements of CopyAssignable and CopyConstructible. В С++ 17 поддерживается std::vector с неподвижными объектами. Означает ли это, что в будущих версиях или с конкретными компиляторами указанная поддержка может прекратиться, поскольку эти контейнеры реализованы с использованием концепций? - person tangy; 22.11.2020
comment
@tangy В этом предложении говорится (до С++ 11). - person Barry; 22.11.2020