web-dev-qa-db-fra.com

Tester si un itérateur pointe vers le dernier élément?

J'ai un itérateur stl résultant d'un std :: find () et je souhaite tester s'il s'agit du dernier élément. Une façon d'écrire ceci est la suivante:

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

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

Mais il me semble que décrémenter l'itérateur end () demande des problèmes, comme si le vecteur n'a pas d'éléments, alors ce ne serait pas défini. Même si je sais qu'il ne sera jamais vide, il me semble toujours moche. Je pense que rbegin () est peut-être la voie à suivre, mais je ne suis pas certain de la meilleure façon de comparer l'itérateur direct avec un itérateur inverse.

30
WilliamKF

Faites ceci:

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

C'est tout. Ne vous donne jamais un comportement indéfini, fonctionne sur tous les itérateurs, bonne journée.

Enveloppez-le pour le plaisir:

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

Donnant:

if (is_last(itr, Mine))

Si vous êtes allergique aux fonctions utilitaires/au joli code, faites:

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

Mais vous ne pouvez pas le faire sur des itérateurs à accès non aléatoire. Celui-ci fonctionne avec des itérateurs bidirectionnels:

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

Et est sûr depuis end() > itr par la première vérification.

56
GManNickG

Oui, il n'est pas sûr de décrémenter (ou incrémenter) end si le vecteur peut être vide. Il est même quelque peu dangereux de faire de même avec un pointeur, même si vous vous en sortirez probablement.

Pour être vraiment sûr, utilisez la soustraction et les valeurs connues pour être sûres et valides:

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

Pour la compatibilité avec tous les itérateurs de transfert (tels que dans slist, par opposition aux itérateurs à accès aléatoire de vector et deque), utilisez

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

ou si vous êtes préoccupé par les performances mais avez des itérateurs bidirectionnels (y compris tout conteneur C++ 03)

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

ou le cas vraiment anal de seuls itérateurs vers l'avant et O(1) temps,

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

ou si vous êtes déterminé à faire preuve d'intelligence pour éviter de nommer la classe itérateur,

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

Pourquoi avez-vous besoin de faire un comportement spécial uniquement si l'élément est le dernier?

Et ça. Le plan consiste simplement à comparer l'adresse de l'élément de l'itérateur avec l'adresse du dernier élément du conteneur, avec une vérification pour s'assurer que l'élément n'est pas déjà la fin (ce qui rend l'appel back sûr):

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

Vous auriez d'abord besoin d'un moyen pour déterminer si un itérateur est un inverseur , ce qui était ingénieusement montré ici :

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

Ensuite, vous pouvez avoir deux saveurs pour effectuer le 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();
    }
};

Et une fonction d'interface unique

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

Ensuite, pour chaque type d'itérateur (inverse/droit), vous pouvez utiliser la fonction d'interface

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

Notez que certaines implémentations (à part std::begin() et std::end() qui sont assez courantes, incluent également std::rbegin() et std::rend(). Lorsque cela est possible, utilisez cet ensemble des fonctions au lieu du membre .begin() etc.

4
Nikos Athanasiou

Si tu fais:

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

Ça devrait être bon. Parce que si itr n'est pas à la fin, il doit y avoir au moins 1 élément dans le conteneur et donc end doit donner un résultat de valeur lorsqu'il est décrémenté.

Mais si vous n'aimez toujours pas cela, il existe de nombreuses façons de faire quelque chose d'équivalent, comme le montrent toutes les autres réponses.

Voici une autre alternative:

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

Voici une autre solution potentielle:

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();
}
2
John Dibling

Essayer de rendre cette réponse aussi simple et polyvalente que possible:

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

Si l'itérateur n'est pas bidirectionnel,

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

Le second crée une copie temporelle de itr et l'incrémente pour tester par rapport à l'itérateur final.

Dans les deux cas, le premier test évite les conteneurs vides pour déclencher une situation indéfinie.

1
Adrian Maire

Une meilleure façon serait de copier l'itérateur puis de l'incrémenter. Vous pouvez ensuite tester la version incrémentée par rapport à end(). Si vous faites attention, vous pouvez utiliser un post-incrément pour éviter d'avoir à le copier officiellement.

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

Si cela pouvait déjà être à la fin:

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

Ou, basé sur la réponse de GMan mais un peu plus sûr:

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

Je viens de corriger le dernier, car je me trompais sur &*.

1
Steven Sudit

Il s'agit essentiellement du même problème que la suppression d'un nœud d'une liste à liaison unique. Vous devez avoir deux itérateurs, l'un qui suit un nœud derrière l'autre, donc lorsque l'itérateur "forward" arrive au nœud que vous souhaitez supprimer (ou quelle que soit l'opération; dans votre cas, le nœud souhaité serait la fin), le " "l'itérateur suivant pointe vers le nœud avant (dans votre cas, ce serait le nœud final).

1
rmeador