Подтвердить что ты не робот

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

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

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

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

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

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

В частности:

  • Учитывается ли это как оператор присваивания копии для T, в соответствии со спецификацией?
  • Учитывается ли это как оператор присваивания перемещения для T в соответствии со спецификацией?
  • Будет ли T иметь оператор присваивания сгенерированного компилятором?
  • Будет ли T иметь оператор присваивания с помощью компилятора?
  • Как это влияет на классы признаков, такие как std::is_move_assignable?
4b9b3361

Ответ 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= будет корректно выполнять ходы, но технически не считать оператором присваивания переадресации. Еще страннее, если класс не имеет конструктора копирования: у него будет оператор присваивания копии, который не делает копии, а только перемещается.