web-dev-qa-db-fra.com

comment portable est décrémentation de l'itérateur de fin?

Je viens de rencontrer une décrémentation de l'itérateur end() dans les codes source de ma société et cela me semble étrange. Pour autant que je m'en souvienne, cela fonctionnait sur certaines plates-formes, mais pas pour les autres. Peut-être que je me trompe, mais je n'ai rien trouvé d'utile en standard à ce sujet. Standard dit seulement que end() renvoie un itérateur qui est la valeur après la fin, mais est-il garanti d'être décrémentable? Comment un code comme celui-ci correspond-il à la norme?

std::list<int>::iterator it = --l.end();

Merci d'avance.

52
ledokol

Je pense que c'est la clause pertinente:

ISO/IEC 14882: 2003 Norme C++ 23.1.1/12 - Séquences

Le tableau 68 répertorie les opérations de séquence fournies pour certains types de conteneurs séquentiels mais pas pour d'autres. Une implémentation doit fournir ces opérations pour tous les types de conteneurs indiqués dans la colonne "conteneur" et les implémenter de manière à prendre un temps constant amorti.

 + -------------------------------------------- -------------------------------- + 
 | Tableau 68 | 
 + -------------- + ----------------- + -------- ------------- + --------------------- + 
 | expression | type de retour | opérationnel | conteneur | 
 | | | sémantique | | 
 + -------------- + ----------------- + ---------- ----------- + --------------------- + 
 | a.front () | référence; | * a.begin () | vecteur, liste, deque | 
 | | const_reference | | | 
 | | pour une constante a | | | 
 + -------------- + ----------------- + ---------- ----------- + --------------------- + 
 | a.back () | référence; | * - a.end () | vecteur, liste, deque | 
 | | const_reference | | | 
 | | pour une constante a | | | 
 ............................................ .................................. 
. . . . . 
. . . . . 
 ............................................ .................................. 
 | a.pop_back () | nul | a.erase (- a.end ()) | vecteur, liste, deque | 
 ....................................... ....................................... 
. . . . . 
. . . . . 

Ainsi, pour les conteneurs répertoriés, non seulement l'itérateur renvoyé par end() doit être décrémentable, l'itérateur décrémenté doit également être déréférencable. (Sauf si le conteneur est vide, bien sûr. Cela invoque un comportement indéfini.)

En fait, les implémentations vector, list et deque fournies avec le compilateur Visual C++ le font exactement comme le tableau. Bien sûr, cela ne signifie pas que chaque compilateur le fait comme ceci:

// From VC++'s <list> implementation

reference back()
    {    // return last element of mutable sequence
    return (*(--end()));
    }

const_reference back() const
    {    // return last element of nonmutable sequence
    return (*(--end()));
    }

Remarque sur le code dans le tableau:

Norme ISO/IEC 14882: 2003 C++ 17.3.1.2/6 - Exigences

Dans certains cas, les exigences sémantiques sont présentées sous forme de code C++. Un tel code est conçu comme une spécification de l'équivalence d'une construction à une autre construction , pas nécessairement comme la façon dont la construction doit être implémentée.

Ainsi, même s'il est vrai qu'une implémentation peut ne pas implémenter ces expressions en termes de begin() et end(), la norme C++ spécifie que les deux expressions sont équivalentes. En d'autres termes, a.back() et *--a.end() sont des constructions équivalentes selon la clause ci-dessus. Il me semble que cela signifie que vous devriez pouvoir remplacer chaque instance de a.back() par *--a.end() et vice-versa et que le code fonctionne toujours.


Selon Bo Persson, la révision de la norme C++ que j'ai sous la main a un défaut par rapport au tableau 68.

Résolution proposée:

Modifiez la spécification du tableau 68 "Opérations de séquence facultatives" dans 23.1.1/12 pour "a.back ()" de

*--a.end()

à

{ iterator tmp = a.end(); --tmp; return *tmp; }

et la spécification pour "a.pop_back ()" de

a.erase(--a.end())

à

{ iterator tmp = a.end(); --tmp; a.erase(tmp); }

Il semble que vous pouvez toujours décrémenter l'itérateur renvoyé par end() et déréférencer l'itérateur décrémenté, tant qu'il n'est pas temporaire.

50
In silico

à partir de la documentation de std::prev

Bien que l'expression --c.end () soit souvent compilée, cela n'est pas garanti: c.end () est une expression rvalue, et il n'y a pas d'exigence d'itérateur qui spécifie que la décrémentation d'une rvalue est garantie pour fonctionner. En particulier, lorsque les itérateurs sont implémentés en tant que pointeurs, --c.end () ne compile pas, contrairement à std :: prev (c.end ()).

ce qui signifie que l'implémentation de l'opération de décrémentation du préfixe peut ne pas être la forme à l'intérieur de la classeiterator iterator::operator--(int) mais surchargée à l'extérieur de la classe iterator operator--(iterator&, int).

vous devez donc soit préférer std::prev soit procéder comme suit: { auto end = a.end(); --end; };

2
Izana

Ce code n'est pas OK, dans le cas où la liste est vide, vous avez des problèmes.

Vérifiez donc cela, si la liste n'est pas vide, le code est très bien.

0