Можем ли мы надежно предварительно увеличить / уменьшить значения?

Например, std::vector<int>::iterator it = --(myVec.end());, Это работает в GCC 4.4, но я слышал слух, что он не переносимый.

5

Решение

Это будет работать только если 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.

8

Другие решения

Нет, это не сработает вообще.

В 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;
}
6

Это действительно не переносимо, потому что нет способа узнать, myVec.end() возвращает объект типа класса с оператором -- перегружен функцией-членом или чем-то еще (может быть, даже обычным необработанным понтером). В первом случае перегруженный -- будет компилироваться (операторы, перегруженные функциями-членами, могут быть применены к значениям), тогда как в последнем случае это не так.

2
По вопросам рекламы [email protected]