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