I'm creating a convenient display()
function template for container types. The output for the last element is different from the rest, thus I check when myIterator != --cont.cend();
. This works for std::vector
, but won't work for std::array
. Why?
Here's a MWE (not my actual code):
std::vector<double> vec({1,2});
std::array<double, 2> arr({{1,2}});
auto vecIt = --vec.end(); // OK
auto arrIt = --arr.end(); // error: lvalue required as decrement operand
Since this is language-lawyer, [expr.pre.increment] and [expr.post.increment] both have the restriction that:
The operand shall be a modifiable lvalue.
Now, neither vec.end()
nor arr.end()
are lvalues, but both of their types are implementation-defined (for array and for vector). In both cases, a simple pointer would satisfy all the iterator requirements for those containers - and this would be a type that uses builtin prefix- and postfix-increment. In that case, --c.end()
would be ill-formed due to the restriction cited. Howver, if the iterator type is a class type, the restriction above doesn't apply - since we're not using the builtin increment operators - and invoking operator--()
on a class does not have this restriction on it (though it could, if the member function were lvalue-reference-qualified).
So --c.end()
for either vector or array isn't guaranteed to work, since if end()
returns a pointer, this is ill-formed, and end()
is allowed to return a pointer. On your particular implementation, vector
's iterator
has class type but array
's iterator
is just a pointer type, which is why the former works but the latter doesn't.
Prefer std::prev(c.end())
, which will work for both container types for all implementations.
Never decrement an rvalue, even if it happens to compile. It's unintuitive for readers of the code.
Use std::prev
instead.
auto it = std::prev(arr.end());
It depends on how the iterator is defined.
It seems that for the class template std::array
the iterator is defined as a pointer. So the functions begin, end. cbegin, cend return just the pointer. Thus as the pointer is returned by value you may not decrease it because an lvalue
is required..
For the class template std::vector
the iterator is defined as a user-defined class for which the operator --() is defined.
Consider the following demonstrative program
#include <iostream>
class Int
{
public:
Int( int x = 0 ) : x ( x )
{
}
Int & operator --()
{
--x;
return *this;
}
friend std::ostream & operator <<( std::ostream &os, const Int &i )
{
return os << i.x;
}
private:
int x;
};
int f( int x )
{
return x;
}
Int f( Int x )
{
return x;
}
int main()
{
std::cout << --f( Int( 10 ) ) << std::endl;
// error: lvalue required as decrement operand
// std::cout << --f( 10 ) << std::endl;
return 0;
}
Take into account that you can write
auto arrIt = std::prev( arr.end() );
instead of
auto arrIt = --arr.end();
provided that you include header <iterator>
.
You can use the operator with the reverse iterators of the class template std::array
because the Standard defines them explicitly like
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
and the user-defined class std::reverse_iterator
defines the operator --()
.
Here is a demonstrative program
#include <iostream>
#include <array>
int main()
{
std::array<double, 2> arr = { { 1, 2 } };
auto it = --arr.rend();
std::cout << *it << std::endl;
return 0;
}
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