Должен ли я использовать спецификаторы ссылочных значений lvalue для операторов присваивания?

Недавно я продолжил обсуждение о назначениях выражениям в С++, как показано в следующем примере:

string s1, s2, s3;
(s1 + s2) = s3;

С С++ 11 можно ограничить оператор присваивания ссылками lvalue (слева). При объявлении операторов присваивания, как следует, компилятор Clang отклоняет код с сообщением об ошибке из-за несовместимых типов.

auto operator=(const string& rhs) & -> string&;
auto operator=(string&& rhs) & -> string&;

Я нигде не видел этого. Есть ли веская причина не использовать ссылочные квалификаторы lvalue для операторов присваивания (помимо отсутствия поддержки в большинстве компиляторов)?

Ответ 1

Нет, не совсем. Использование квалификаторов lvalue или rvalue для построения правильного интерфейса для объектов lvalue или rvalue аналогично использованию const, и к нему следует подходить одинаково - каждая функция должна рассматриваться для ограничения. Назначение rvalue на самом деле не имеет смысла, поэтому это должно быть запрещено.

Причина, по которой вы ее не видели, - это в основном плохая поддержка компилятора - refval refs для *this похожа на thread_local, большинство компиляторов, похоже, поставили ее в нижней части "Возможности для реализации с C + +11".

Ответ 2

Интересно! Я даже не знал об этом и взял меня, чтобы найти его (это было частью Расширение семантики перемещения к этому * предложению), Обозначение определяется в пункте 4.3.5 [dcl.decl], если кто-то хочет посмотреть.

В любом случае: теперь, зная об этой функции, кажется, что наиболее полезно использовать ее для перегрузки и, возможно, вести себя по-другому, если объект, на который вызывается функция, является значением lvalue или rvalue. Использование его для ограничения того, что можно сделать, например, с результатом назначения, кажется ненужным, особенно если объект на самом деле является lvalue. Например, вы можете захотеть, чтобы синтаксис возвращал значение rvalue из присвоения rvalue:

struct T {
    auto operator=(T&) & -> T&;
    auto operator=(T&&) & -> T&;
    auto operator=(T&) && -> T;
    auto operator=(T&&) && -> T;
};

Цель здесь состояла в том, чтобы разрешить переход от результата присваивания (независимо от того, стоит ли это, хотя я не уверен: почему бы не пропустить присвоение в первую очередь?). Я не думаю, что я использовал бы эту функцию прежде всего для ограничения использования.

Лично мне нравится возможность иногда получать lvalue из rvalue, и оператор присваивания часто является способом сделать это. Например, если вам нужно передать значение lvalue функции, но вы знаете, что не хотите использовать что-либо с ней, вы можете использовать оператор присваивания для получения значения lvalue:

#include <vector>
void f(std::vector<int>&);
int main()
{
    f(std::vector<int>() = std::vector<int>(10));
}

Это может быть злоупотребление оператором присваивания, чтобы получить значение lvalue от rvalue, но это вряд ли произойдет случайно. Таким образом, я не собираюсь сходить с пути и сделать это невозможным, ограничивая оператор присваивания применимым только к lvalues. Конечно, возврат этого значения из присвоения к rvalue также предотвратит это. Какое из двух применений более полезно, если таковое имеется, может быть рассмотрено.

Кстати, clang, похоже, поддерживает синтаксис, который вы цитировали с версии 2.9.

Ответ 3

Одна из причин, по которой я не являюсь супер энтузиастом по поводу вашего предложения, заключается в том, что Я стараюсь уклониться от объявления специальных участников вообще. Таким образом, большинство моих операторов присваивания неявно объявлены и, следовательно, не имеют ref-квалификаторов.

Конечно, для тех случаев, когда я пишу шаблон класса или класса, например. (см. вывод в ссылке выше), я мог бы позаботиться об объявлении этих операторов только для lvalues. Поскольку он не влияет на клиентов, однако, не так много.