For example, std::vector<int>::iterator it = --(myVec.end());
. This works in GCC 4.4 but I have heard a rumor that it's not portable.
It will only work if std::vector<int>::iterator
is an object type with operator++
a member function. If it's a scalar type (e.g. int *
), or operator++
is a non-member function, it will fail.
5.3.2 Increment and decrement [expr.pre.incr]
1 - The operand of prefix
++
is modified by adding1
[...]. The operand shall be a modifiable lvalue. [...]
2 - [...] The requirements on the operand of prefix--
[...] are [...] the same as those of prefix++
. [...]
Non-const non-static member functions can be called on temporary objects (since they have non-const
object type, per 9.3.2p3), but an lvalue reference parameter in a non-member function cannot bind to a temporary (13.3.3.1.4p3).
struct S { S &operator++(); };
struct T { }; T &operator++(T &);
typedef int U;
++S(); // OK
++T(); // fails
++U(); // fails
This means that it's nothing to do with the compiler, but rather the standard library; as you've observed libstdc++ is implemented with std::vector<int>::iterator
an object type with member operator++
, but your code could easily be compiled with the same compiler and a different standard library where std::vector<int>::iterator
is int *
, in which case it would fail.
std::vector
, std::array
and std::string
are the only container templates that can sensibly be implemented with scalar (pointer) iterators, but that doesn't mean that calling ++
on other containers' iterators is safe; they could have non-member operator++
as T
above.
To make an iterator to the before-the-end element, use std::prev
:
std::vector<int>::iterator it = std::prev(myVec.end());
std::prev
and std::next
are new in C++11, but are easily implementable in C++03.
No that won't work in general.
In C++11 we have: auto it = std::prev(myVec.end());
, which works reliably.
Boost has a similar function if you're in C++03, though it's trivial to write altogether:
template <typename BidirectionalIterator>
BidirectionalIterator
prev(BidirectionalIterator x,
typename std::iterator_traits<BidirectionalIterator>::difference_type n = 1)
{
std::advance(x, -n);
return x;
}
Keep in mind you need at least one element in the range for this to make sense.
Here's an example of how your method won't work in general, consider this stripped-down 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;
}
}
Test program:
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());
}
There is a corresponding std::next
as well, implemented here:
template <typename BidirectionalIterator>
BidirectionalIterator
next(BidirectionalIterator x,
typename std::iterator_traits<BidirectionalIterator>::difference_type n = 1)
{
std::advance(x, n);
return x;
}
This is indeed not portable, because there's no way to know whether myVec.end()
returns an object of class type with operator --
overloaded by a member function or something else (maybe even a regular raw ponter). In the former case the overloaded --
will compile (operators overloaded by member functions can be applied to rvalues), while in the latter case it will not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With