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

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

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

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

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

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

3 ответа

Решение

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

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

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

В любом случае: теперь, зная об этой функции, кажется, что наиболее полезно использовать ее для перегрузки и, возможно, вести себя по-разному, если объект, для которого вызывается функция, является 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 из присваивания в rvalue также предотвратит это. Какое из двух использований более полезно, если таковые имеются, может быть вопрос.

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

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

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

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