Оператор перегрузки [] и НЕ получает ошибку «lvalue требуется как левый операнд присваивания» - PullRequest
16 голосов
/ 18 июня 2019

Это немного инверсия всех вопросов об ошибках "lvalue Требуется как левый операнд присваивания".
У меня есть класс, который перегружает operator [], но только версия, которая возвращает временное значение.Если бы он возвращал int:

struct Foo
{
    int operator[]( int idx ) const { return int( 0 ); }
};

Foo f;
f[1] = 5;

, я бы по праву получил ошибку компилятора lvalue.Однако, если он возвращает тип структуры, компилятор (в данном случае GCC 7.2) вообще не будет жаловаться:

struct Bar {};
struct Foo
{
    Bar operator[]( int idx ) const { return Bar(); }
};

Foo f;
f[1] = Bar();

Почему бы не жаловаться таким же образом, если Bar является временным и не имеетспециализированный оператор =?Альтернативный вопрос, есть ли способ заставить эту жалобу?Очевидно, что это ошибка кодирования, если ее использовать таким образом

Ответы [ 2 ]

16 голосов
/ 18 июня 2019

есть ли способ заставить эту жалобу?

Вы можете использовать явно заданный по умолчанию оператор присваивания с квалификатором ref:

struct Bar {
    Bar& operator=(const Bar&) & = default;
//                             ^

Это делает присвоение r-значения плохо сформированным, в то время как назначение l-значения остается корректным.Formed.

Обратите внимание, что объявление оператора присваивания отключает неявное присваивание перемещения, поэтому вам может потребоваться определить это также, если необходимо (также по умолчанию, и, возможно, с квалификатором rvalue ref, если необходимо).

Почему бы так не жаловаться, если Bar является временным и у него нет специального оператора =?

Поскольку неявно сгенерированные операторы присваивания не являются ref-квалифицированными.

Очевидно, что это ошибка кодирования, если ее использовать таким образом

Присвоение значения r не всегда является ошибкой.Для некоторых типов, которые должны вести себя как ссылки, присваивание значения r является естественным.Это связано с тем, что присвоение изменяет указанный объект, а не сам временный объект.

Типичным случаем использования является присвоение rvalue std::tie (пример из cppreference ):

std::set<S> set_of_s; // S is LessThanComparable

S value{42, "Test", 3.14};
std::set<S>::iterator iter;
bool inserted;

// unpacks the return value of insert into iter and inserted
std::tie(iter, inserted) = set_of_s.insert(value);

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

1 голос
/ 18 июня 2019

Да, есть способ превратить это в ошибку компиляции, удалив эти методы:

Bar& operator=(const Bar&)&& =delete;
Bar& operator=(Bar&&)&& =delete;

Просто обратите внимание, что это отключит автогенерацию других операторов и конструкторов, поэтому вам нужно определить ихвсе:

struct Bar {
    Bar()=default;
    Bar(const Bar&) = default;
    Bar& operator=(const Bar&)&& =delete;
    Bar& operator=(Bar&&)&& =delete;
    Bar& operator=(const Bar&)& =default;
    Bar& operator=(Bar&&)& =default;
};
...