Например, std::vector<int>::iterator it = --(myVec.end());
, Это работает в GCC 4.4, но я слышал слух, что он не переносимый.
Это будет работать только если std::vector<int>::iterator
это тип объекта с operator++
функция-член. Если это скалярный тип (например, int *
), или же operator++
не является функцией-членом, она потерпит неудачу.
5.3.2 Увеличение и уменьшение [expr.pre.incr]
1 — операнд префикса
++
модифицируется добавлением1
[…]. Операндом должно быть изменяемое значение l. […] 2 — […] Требования к операнду префикса--
[…] такие же, как и у префикса++
, […]
Неконстантные нестатические функции-члены могут вызываться для временных объектов (так как они имеютconst
тип объекта, согласно 9.3.2p3), но ссылочный параметр lvalue в функции, не являющейся членом, не может быть привязан к временному (13.3.3.1.4p3).
struct S { S &operator++(); };
struct T { }; T &operator++(T &);
typedef int U;
++S(); // OK
++T(); // fails
++U(); // fails
Это означает, что это не имеет ничего общего с компилятором, а скорее со стандартной библиотекой; как вы заметили, libstdc ++ реализован с std::vector<int>::iterator
тип объекта с членом operator++
, но ваш код может быть легко скомпилирован с тем же компилятором и другой стандартной библиотекой, где std::vector<int>::iterator
является int *
в этом случае это не получится.
std::vector
, std::array
а также std::string
являются единственными шаблонами контейнеров, которые могут быть разумно реализованы с помощью скалярных (указательных) итераторов, но это не означает, что вызов ++
на других контейнерах итераторы безопасны; они могут иметь не-членов operator++
как T
выше.
Чтобы сделать итератор для элемента before-the-end, используйте std::prev
:
std::vector<int>::iterator it = std::prev(myVec.end());
std::prev
а также std::next
являются новыми в C ++ 11, но легко реализуемы в C ++ 03.
Нет, это не сработает вообще.
В C ++ 11 мы имеем: auto it = std::prev(myVec.end());
, который работает надежно.
Boost имеет аналогичную функцию, если вы находитесь в C ++ 03, хотя писать тривиально:
template <typename BidirectionalIterator>
BidirectionalIterator
prev(BidirectionalIterator x,
typename std::iterator_traits<BidirectionalIterator>::difference_type n = 1)
{
std::advance(x, -n);
return x;
}
Имейте в виду, что вам нужен хотя бы один элемент в диапазоне, чтобы это имело смысл.
Вот пример того, как ваш метод не будет работать в общем, рассмотрим этот урезанный std::vector<>
:
#include <iterator>
namespace std_exposition
{
template <typename T>
struct vector
{
// this is compliant:
typedef T* iterator;
iterator end()
{
return std::end(data);
}
T data[4];
};
// manually implemented std::prev:
template <typename BidirectionalIterator>
BidirectionalIterator
prev(BidirectionalIterator x,
typename std::iterator_traits<BidirectionalIterator>::difference_type n = 1)
{
std::advance(x, -n);
return x;
}
}
Тестовая программа:
int main()
{
std_exposition::vector<int> myVec;
// Won't compile (method in question):
auto it0 = --(myVec.end());
// Compiles
auto it1 = std::prev(myVec.end());
auto it2 = std_exposition::prev(myVec.end());
}
Есть соответствующий std::next
а также реализовано здесь:
template <typename BidirectionalIterator>
BidirectionalIterator
next(BidirectionalIterator x,
typename std::iterator_traits<BidirectionalIterator>::difference_type n = 1)
{
std::advance(x, n);
return x;
}
Это действительно не переносимо, потому что нет способа узнать, myVec.end()
возвращает объект типа класса с оператором --
перегружен функцией-членом или чем-то еще (может быть, даже обычным необработанным понтером). В первом случае перегруженный --
будет компилироваться (операторы, перегруженные функциями-членами, могут быть применены к значениям), тогда как в последнем случае это не так.