Почему оператор не перегружен для lvalues ​​и rvalues?

Стандартные контейнеры С++ предлагают только одну версию operator[] для контейнеров, таких как vector<T> и deque<T>. Он возвращает T& (кроме vector<bool>, который я проигнорирую), что является lvalue. Это означает, что в коде, подобном этому,

vector<BigObject> makeVector();       // factory function

auto copyOfObject = makeVector()[0];  // copy BigObject

copyOfObject будет построена копия. Учитывая, что makeVector() возвращает rvalue vector, представляется разумным ожидать, что конструкция copyOfObject будет построена.

Если operator[] для таких контейнеров был перегружен для объектов rvalue и lvalue, то operator[] для контейнеров rvalue мог возвращать ссылку rvalue, то есть rvalue:

template<typename T>
container {
public:
    T& operator[](int index) &;       // for lvalue objects
    T&& operator[](int index) &&;     // for rvalue objects
...
};

В этом случае copyOfObject будет построено движение.

Есть ли причина, по которой такая перегрузка будет плохой идеей вообще? Есть ли причина, почему это не было сделано для стандартных контейнеров в С++ 14?

Ответы

Ответ 1

Преобразование комментария в ответ:

В этом подходе нет ничего неправильного; доступ к члену класса соответствует аналогичному правилу (E1.E2 является значением xvalue, если E1 является rvalue, а E2 обозначает нестатический элемент данных и не является ссылкой, см. [expr.ref]/4.2) и элементами внутри контейнера логически похожи на нестатические элементы данных.

Значительная проблема с выполнением этого для std::vector или других стандартных контейнеров заключается в том, что, скорее всего, он сломает некоторый старый код. Рассмотрим:

void foo(int &);
std::vector<int> bar();

foo(bar()[0]);

Эта последняя строка прекратит компиляцию, если operator[] в векторе rvalue возвращает значение x. В качестве альтернативы - и, возможно, хуже - если есть перегрузка foo(const int &), она беззвучно начнет вызывать эту функцию.

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

Ответ 2

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

T copyObj = std::move(makeVector()[0]);

Update:

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