Why can't I decrement std::array::end()?

核能气质少年 提交于 2019-12-01 03:41:14

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

Since this is , [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());
Bathsheba

It's a fact of life from the C++ standard.

--T.end() is required to work if T is a std::vector, a std::list, or a std::deque. This was correct up to and including C++11; there have been relaxations subsequent to that.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!