Почему оператор ++ возвращает неконстантное значение?
Я прочитал "Эффективное С++ 3rd Edition", написанное Скоттом Мейерсом.
Пункт 3 книги "Использовать const
, когда это возможно", говорит, что если мы хотим предотвратить случайное присвоение значения rval для возвращаемого значения функции, тип возврата должен быть const
.
Например, функция приращения для iterator
:
const iterator iterator::operator++(int) {
...
}
Затем предотвращаются некоторые несчастные случаи.
iterator it;
// error in the following, same as primitive pointer
// I wanted to compare iterators
if (it++ = iterator()) {
...
}
Однако итераторы, такие как std::vector::iterator
в GCC, не возвращают значения const
.
vector<int> v;
v.begin()++ = v.begin(); // pass compiler check
Есть ли причины для этого?
Ответы
Ответ 1
Я уверен, что это связано с тем, что он будет играть хаос с ссылками rvalue и любым типом decltype
. Несмотря на то, что эти функции не были в С++ 03, они, как известно, уже приближаются.
Что еще более важно, я не считаю, что любая стандартная функция возвращает const rvalues, возможно, это то, что не рассматривалось до публикации стандарта. Кроме того, константные значения обычно не считаются правильными. Не все использования не-const-функций-членов недопустимы, а возвращаемые константы-значения полностью предотвращают их.
Например,
auto it = ++vec.begin();
является вполне допустимым и действительно действительной семантикой, если это не совсем желательно. Рассмотрим мой класс, который предлагает цепочки методов.
class ILikeMethodChains {
public:
int i;
ILikeMethodChains& SetSomeInt(int param) {
i = param;
return *this;
}
};
ILikeMethodChains func() { ... }
ILikeMethodChains var = func().SetSomeInt(1);
Если это запрещено, просто потому, что иногда мы можем назвать функцию, которая не имеет смысла? Нет, конечно нет. Или как насчет "swaptimization"?
std::string func() { return "Hello World!"; }
std::string s;
func().swap(s);
Это было бы незаконным, если func()
создало выражение const, но оно совершенно корректно и действительно, предполагая, что реализация std::string
не выделяет никакой памяти в конструкторе по умолчанию, как быстро, так и читабельно/читабельно.
Что вы должны понимать, так это то, что правила rvalue/lvalue С++ 03 откровенно просто не имеют смысла. Они, фактически, только частично испечены, и минимум, необходимый для того, чтобы запретить некоторые вопиющие ошибки, разрешая некоторые возможные права. Правила С++ 0x rvalue намного более понятны и намного более полны.
Ответ 2
Если it
не const const, я ожидаю, что *(++it)
предоставит мне изменяемый доступ к тому, что он представляет.
Однако разыменование итератора const
дает только не изменяемый доступ к предмету, который он представляет. [ изменить: нет, это тоже неправильно. Я действительно сдаюсь!]
Это единственная причина, по которой я могу думать.
Как вы справедливо отметили, следующее неверно сформировано, потому что ++
на примитиве дает значение r (которое не может быть на LHS):
int* p = 0;
(p++)++;
Таким образом, на этом языке, кажется, есть что-то несоответствие.
Ответ 3
EDIT. Это не отвечает на вопрос, как указано в комментариях. Я просто оставлю сообщение здесь, если это так полезно...
Я думаю, что это в значительной степени вопрос объединения синтаксиса в сторону более удобного интерфейса. Предоставляя такие функции-члены без различия имени и позволяя только механизму разрешения перегрузки определять правильную версию, вы предотвращаете (или, по крайней мере, пытаться) программист от проблем, связанных с const
.
Я знаю, что это может показаться противоречивым, особенно с учетом вашего примера. Но если вы думаете о большинстве случаев использования, это имеет смысл. Возьмите алгоритм STL, например std::equal
. Независимо от того, является ли ваш контейнер постоянным или нет, вы всегда можете кодировать что-то вроде bool e = std::equal(c.begin(), c.end(), c2.begin())
, не задумываясь о правильной версии начала и конца.
Это общий подход в STL. Помните operator[]
... Имея в виду, что контейнеры должны использоваться с алгоритмами, это правдоподобно. Хотя также заметно, что в некоторых случаях вам может потребоваться определить итератор с подходящей версией (iterator
или const_iterator
).
Хорошо, это то, что приходит мне на ум прямо сейчас. Я не уверен, насколько это убедительно...
Боковое примечание. Правильный способ использования постоянных итераторов - это const_iterator
typedef.