Ветвление операторов присваивания со значениями вместо ссылок

Этот вопрос возникает из вопросов, поднятых этим ответом.

Обычно мы определяем операторы копирования для типа T как T& operator=(const T&)и переместить операторы присваивания для типа T как T& operator=(T&&),

Однако что происходит, когда мы используем параметр-значение, а не ссылку?

class T
{
public:
  T& operator=(T t);
};

Это должно сделать T копируемым и перемещаемым. Тем не менее, я хочу знать, каковы языковые последствия для T?

В частности:

  1. Считается ли это оператором копирования для Tсогласно спецификации?
  2. Считается ли это оператором перемещения для Tсогласно спецификации?
  3. Будет T есть сгенерированный компилятором оператор присваивания копии?
  4. Будет T есть сгенерированный компилятором оператор присваивания перемещения?
  5. Как это влияет на черты классов, такие как std::is_move_assignable?

1 ответ

Решение

Большая часть этого описана в §12.8. Параграф 17 определяет, что считается объявленными пользователем операторами копирования:

Объявленный пользователем оператор копирования X::operator= является нестатической не шаблонной функцией-членом класса X с ровно одним параметром типа X, X&, const X&, volatile X&, или же const volatile X&,

Пункт 19 определяет то, что считается объявленными пользователем операторами перемещения:

Объявленный пользователем оператор присваивания перемещения X::operator= является нестатической не шаблонной функцией-членом класса X с ровно одним параметром типа X&&, const X&&, volatile X&&, или же const volatile X&&,

Таким образом, он считается оператором назначения копирования, но не оператором перемещения.

Параграф 18 сообщает, когда компилятор генерирует операторы копирования:

Если определение класса явно не объявляет оператор присваивания копии, он объявляется неявно. Если определение класса объявляет конструктор перемещения или оператор присваивания перемещения, неявно объявленный оператор присваивания копии определяется как удаленный; в противном случае он определяется как дефолтный (8.4). Последний случай считается устаревшим, если в классе имеется объявленный пользователем конструктор копирования или объявленный пользователем деструктор.

Параграф 20 говорит нам, когда компилятор генерирует операторы присваивания перемещения:

Если определение класса X явно не объявляет оператор присваивания перемещения, он будет неявно объявлен как дефолтный, если и только если
[...]
- X не имеет заявленного пользователем оператора копирования,
[...]

Поскольку класс имеет объявленный пользователем оператор копирования, ни один из неявных не будет сгенерирован компилятором.

std::is_copy_assignable а также std::is_move_assignable описаны в таблице 49 как имеющие то же значение, что и соответственно is_assignable<T&,T const&>::value а также is_assignable<T&,T&&>::value, Эта таблица говорит нам, что is_assignable<T,U>::value является true когда:

Выражение declval<T>() = declval<U>() является правильно сформированным, когда рассматривается как неоцененный операнд (пункт 5). Проверка доступа выполняется как в контексте, не связанном с T а также U, Учитывается только достоверность непосредственного контекста выражения присваивания.

Поскольку оба declval<T&>() = declval<T const&>() а также declval<T&>() = declval<T&&>() правильно сформированы для этого класса, он по-прежнему считается назначаемым для копирования и назначаемым для перемещения.

Как я уже упоминал в комментариях, любопытно, что в присутствии конструктора перемещения это operator= будет правильно выполнять ходы, но технически не считается оператором назначения ходов. Это даже странно, если у класса нет конструктора копирования: у него будет оператор присваивания копии, который не делает копии, а только перемещает.

Другие вопросы по тегам