开发者

Testing whether an iterator points to the last item?

开发者 https://www.devze.com 2023-01-11 22:55 出处:网络
I have an stl iterator resulting from a std::find() and wish to test whether it is the last element.One way to write this is as follows:

I have an stl iterator resulting from a std::find() and wish to test whether it is the last element. One way to write this is as follows:

mine *match = someValue;
vector<mine *> Mine(someContent);
vector<mine *>::iterator itr = std::find(Mine.begin(), Mine.end(), match);

if (itr == --Mine.end()) {
  doSomething;
}

But it seems开发者_StackOverflow社区 to me that decrementing the end() iterator is asking for trouble, such as if the vector has no elements, then it would be undefined. Even if I know it will never be empty, it still seems ugly. I'm thinking that maybe rbegin() is the way to go, but am not certain as to best way to compare the forward iterator with a reverse iterator.


Do this:

// defined in boost/utility.hpp, by the way
template <typename Iter>
Iter next(Iter iter)
{
    return ++iter;
}

// first check we aren't going to kill ourselves
// then check if the iterator after itr is the end
if ((itr != Mine.end()) && (next(itr) == Mine.end()))
{
    // points at the last element
}

That is all. Never gives you undefined behavior, works on all iterators, good day.

Wrap it up for fun:

template <typename Iter, typename Cont>
bool is_last(Iter iter, const Cont& cont)
{
    return (iter != cont.end()) && (next(iter) == cont.end())
}

Giving:

if (is_last(itr, Mine))

If you're allergic to utility functions/nice looking code, do:

if ((itr != Mine.end()) && (itr + 1 == Mine.end()))

But you can't do it on non-random-access iterators. This one works with bidirectional iterators:

if ((itr != Mine.end()) && (itr == --Mine.end()))

And is safe since end() > itr by the first check.


Yes, it's unsafe to decrement (or increment) end if the vector may be empty. It's even somewhat unsafe to do the same with a pointer, although you'll probably get away with it.

To be really safe, use subtraction and values known to be safe and valid:

if ( Mine.end() - itr == 1 )

For compatibility with all forward iterators (such as in slist, as opposed to random-access iterators of vector and deque), use

if ( std::distance( itr, Mine.end() ) == 1 )

or if you are concerned with performance but have bidirectional iterators (incl. any C++03 container)

if ( itr != Mine.end() && itr == -- Mine.end() )

or the truly anal case of only forward iterators and O(1) time,

if ( itr != Mine.end() && ++ container::iterator( itr ) == Mine.end() )

or if you are hellbent on cleverness to avoid naming the iterator class,

if ( itr != Mine.end() && ++ ( Mine.begin() = itr ) == Mine.end() )


Why do you need to do special behavior only if the item is the last one?

What about this. The plan is just to compare the address of the iterator's item with the address of the last item in the container, with a check to make sure the item is actually not already the end (making the back call safe):

if (itr != Mine.end() && &*itr == &Mine.back()) {
  doSomething;
}


You would first need a way to determine if an iterator is a reverse one, which was ingeniously shown here :

#include <iterator>
#include <type_traits>

template<typename Iter>
struct is_reverse_iterator : std::false_type { };

template<typename Iter>
struct is_reverse_iterator<std::reverse_iterator<Iter>>
: std::integral_constant<bool, !is_reverse_iterator<Iter>::value>
{ };

Then you could have two flavors for performing the test

template<bool isRev> // for normal iterators
struct is_last_it
{
    template<typename It, typename Cont>
    static bool apply(It it, Cont const &cont)
    { // you need to test with .end()
        return it != cont.end() && ++it == cont.end();
    }
};

template<> // for reverse iterators
struct is_last_it<true>
{
    template<typename It, typename Cont>
    static bool apply(It it, Cont const &cont)
    { // you need to test with .rend()
        return it != cont.rend() && ++it == cont.rend();
    }
};

And a single interface function

template<typename It, typename Cont>
bool is_last_iterator(It it, Cont const &cont)
{
    return is_last_it<is_reverse_iterator<It>::value>::apply(it, cont);
};

Then for every type of iterator (reverse / straight) you can use the interface function

int main()
{
    std::vector<int> v;
    v.push_back(1);

    auto it (v.begin()),  ite(v.end());   // normal iterators
    auto rit(v.rbegin()), rite(v.rend()); // reverse iterators

    std::cout << is_last_iterator(it, v) << std::endl;
    std::cout << is_last_iterator(ite, v) << std::endl;
    std::cout << is_last_iterator(rit, v) << std::endl;
    std::cout << is_last_iterator(rite, v) << std::endl;

    return 0;
}

Note that some implementation (apart from std::begin() and std::end() which are common enough, also include std::rbegin() and std::rend(). When possible use this set of functions instead of member .begin() etc.


If you do:

if(itr != Mine.end() && itr == --Mine.end())

It should be fine. Because if itr is not at the end then there must be at least 1 element in the container and so end must yield a value result when decremented.

But if you still don't like that, there are lots of ways to do something equivalent, as all the other answers show.

Here's another alternative:

if(itr != Mine.end() && std::distance(Mine.begin(), itr) == Mine.size()-1)


Here's another potential solution:

template<class Iterator, class Container> bool is_last(Iterator it, const Container& cont)
{
    // REQUIREMENTS:
    // the iterator must be a valid iterator for `cont`
    if( it == cont.end() )
        return false;   // or throw if you prefer
    return (++it) == cont.end();
}


Trying to make this answer as simple and versatile as possible:

if( itr!=Mine.end() && itr== --Mine.end())

If the iterator is not bi-directional,

if( itr!=Min.end() && ++decltype(itr)(itr)==Mine.end())

The second create a temporal copy of itr and increment it to test against the end iterator.

In both cases, the first test avoid empty containers to trigger an undefined situation.


This is essentially the same problem as deleting a node from a singly-linked list. You have to have two iterators, one that follows one node behind the other, so when the "forward" iterator gets to the node you want to delete (or whatever operation; in your case the desired node would be the end), the "following" iterator is pointing to the node before (in your case, this would be the final node).


A better way would be to copy the iterator and then increment it. You can then test the incremented version against end(). If you're careful, you can use a post-increment to avoid the need to formally copy it.

  if (++vector<mine*>::iterator(itr) == Mine.end())

If itr could already be at the end:

  if (itr == Mine.end() || ++vector<mine*>::iterator(itr) == Mine.end())

Or, based on GMan's answer but a bit safer:

  if (Mine.Length() == 0 || itr == Mine.End() || &*itr == &Mine.back())

I just fixed the last one again, as I was wrong about &*.

0

精彩评论

暂无评论...
验证码 换一张
取 消