Oбычно в индексах массивов, контейнеров используется беззнаковое число. К тому же unsigned(-1) компилятором преобразовывается на большое положительное число
Так почему же в индексах отрицательное число дает не такой эффект?
Например:
int n[] = {1, 2, 3 };
int* pn = &n[1];
if (n - (&n[-1]) == 1)
std::cout << pn[-1]; // выведет n[1 - 1] т.е. n[0]
или
std::vector<int>::end()[-1] то же, что std::vector<int>::back();
Все стает просто, если знать, что конструкция a[b] - это всего навсего *(a+b). Поэтому, выражение
int* pn = &n[1];
преобразуется в
int* pn = &(*(n+1));
int* pn = n+1;
и потом выражение
pn[-1]
преобразуется в *(pn-1) => *(n+1-1) => *(n+0) => n[0].
Аналогично расскрывается и выражение n - (&n[-1]).
В случае вектора компилятор может итератор заменить на указатель и наблюдается та же история. Но как только компилятор чуточку сделает по другому (а он имеет право), может получится все, что угодно.
Параметр оператора [] стандартного контейнера, как вы правильно заметили, имеет беззнаковый тип. Соответственно аргумент такого оператора при вызове будет приводиться к беззнаковому типу.
Параметр встроенного оператора [] не имеет беззнакового типа и не приводится к беззнаковому типу. Встроенный оператор [] прекрасно умеет работать и с отрицательными значениями, пока вы соблюдаете правила адресной арифметики языка.
(В целях overload resolution, встроенный оператор [] рассматривается как имеющий параметр типа std::ptrdiff_t, который является знаковым)
Правила адресной арифметики говорят, что адресная арифметика в С и С++ поддерживается только среди элементов одного массива. Соответственно отрицательные индексы в [] могут быть использованы только в том случае, если доступ осуществляется относительно указателя, указывающего куда-то "в середину" существующего массива.
В вашем примере кода выражение pn[-1] удовлетворяет этим правилам, а в выражении n[-1] это правило нарушено. Выражение n[-1] порождает неопределенное поведение. Код формально неработоспособен.
Выражение вида some_vector.end()[-1] формально некорректно. К итератору контейнера в общем случае вообще не применим оператор []. Скомпилироваться такое выражение может только случайно за счет того, что в некоторой реализации
итераторы std::vector оказались реализованы как обычные "голые" указатели.
Попыткой создания корректной формы такого выражения может быть (&*some_vector.end())[-1], т.е. преобразование итератора в "голый" указатель, и последующее применение к нему оператора []. Но в таком случае мы применяем оператор * к итератору some_vector.end(), что порождает неопределенное поведение.
Так что пытаться индексироваться "назад" от end() итератора при помощи опратора [] - плохая идея. Пользуйтесь для этого std::next/std::prev.
Сборка персонального компьютера от Artline: умный выбор для современных пользователей