Supposons que j'ai le code suivant :
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
Puis-je trouver la position de elem
dans le vecteur sans maintenir un itérateur séparé ?
Supposons que j'ai le code suivant :
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
Puis-je trouver la position de elem
dans le vecteur sans maintenir un itérateur séparé ?
Oui, vous pouvez, il faut juste un peu de massage ;)
L'astuce consiste à utiliser la composition : au lieu d'itérer sur le conteneur directement, vous le "zappez" avec un index en cours de route.
Code de la fermeture éclair spécialisée :
template <typename T>
struct iterator_extractor { typedef typename T::iterator type; };
template <typename T>
struct iterator_extractor<T const> { typedef typename T::const_iterator type; };
template <typename T>
class Indexer {
public:
class iterator {
typedef typename iterator_extractor<T>::type inner_iterator;
typedef typename std::iterator_traits<inner_iterator>::reference inner_reference;
public:
typedef std::pair<size_t, inner_reference> reference;
iterator(inner_iterator it): _pos(0), _it(it) {}
reference operator*() const { return reference(_pos, *_it); }
iterator& operator++() { ++_pos; ++_it; return *this; }
iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; }
bool operator==(iterator const& it) const { return _it == it._it; }
bool operator!=(iterator const& it) const { return !(*this == it); }
private:
size_t _pos;
inner_iterator _it;
};
Indexer(T& t): _container(t) {}
iterator begin() const { return iterator(_container.begin()); }
iterator end() const { return iterator(_container.end()); }
private:
T& _container;
}; // class Indexer
template <typename T>
Indexer<T> index(T& t) { return Indexer<T>(t); }
Et l'utiliser :
#include <iostream>
#include <iterator>
#include <limits>
#include <vector>
// Zipper code here
int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};
for (auto p: index(v)) {
std::cout << p.first << ": " << p.second << "\n";
}
}
Vous pouvez le voir sur idéone bien qu'il manque le support de la boucle for-range et que ce soit moins joli.
EDITAR:
Je viens de me rappeler que je devrais vérifier Boost.Range plus souvent. Malheureusement pas zip
mais j'ai trouvé une perle : boost::adaptors::indexed
. Cependant, il faut accéder à l'itérateur pour extraire l'index. Dommage :x
Sinon, avec le counting_range
et un générique zip
Je suis sûr qu'il serait possible de faire quelque chose d'intéressant...
Dans un monde idéal, j'imagine :
int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};
for (auto tuple: zip(iota(0), v)) {
std::cout << tuple.at<0>() << ": " << tuple.at<1>() << "\n";
}
}
Avec zip
créer automatiquement une vue comme une gamme de tuples de références et de iota(0)
en créant simplement une "fausse" gamme qui commence à partir de 0
et compte simplement vers l'infini (ou bien, le maximum de son type...).
@ildjarn : Oui, Boost.Iterators a les blocs de construction (il semble), cependant il n'y a pas de gamme correspondante, ce qui est ennuyeux.
@Xeo Votre version fonctionne bien pour les lvalues (en effet, comme vous l'avez dit, aucune copie n'a lieu). Pour rvalues il y a un problème, cependant. Je ne l'ai pas encore repéré, mais je vais continuer à l'examiner demain. En gros, lorsque j'utilise index
comme ceci for (auto x : index(std::vector<int>{2, 4, 6})) { ... }
Je reçois cette erreur : error: no matching function for call to ‘Indexer<std::vector<int, std::allocator<int> > >::iterator::iterator(std::vector<int, std::allocator<int> >::const_iterator)’
. J'ai utilisé g++-4.7.
Jrok a raison : les boucles d'arrêt basées sur la distance ne sont pas conçues dans ce but.
Cependant, dans votre cas, il est possible de le calculer en utilisant l'arithmétique des pointeurs puisque vector
stocke ses éléments de manière contiguë (*)
vector<int> list;
for(auto& elem:list) {
int i = elem;
int pos = &elem-&list[0]; // pos contains the position in the vector
// also a &-operator overload proof alternative (thanks to ildjarn) :
// int pos = addressof(elem)-addressof(list[0]);
}
Mais c'est clairement une mauvaise pratique car elle obscurcit le code et le rend plus fragile (il se casse facilement si quelqu'un change le type de conteneur, surcharge la fonction &
ou remplacer 'auto&' par 'auto'. Bonne chance pour déboguer cela !)
NOTE : La contiguïté est garantie pour les vecteurs dans la norme C++03, et pour les tableaux et les chaînes dans la norme C++11.
Oui, c'est indiqué dans la norme. La contiguïté est garantie pour vector
en C++03, et array
y string
en C++11.
" il se brise facilement si quelqu'un ... surcharge le &
opérateur " C'est ce que std::addressof
est pour. :-]
Vous avez raison. Donc la version prouvée par &-overload serait : int pos = addressof(elem)- addressof(list[0]) ; .... Le wrapper d'itérateur de Matthieu M. est bien meilleur :)
Non, vous ne pouvez pas (du moins, pas sans effort). Si vous avez besoin de la position d'un élément, vous ne devriez pas utiliser la méthode range-based for. N'oubliez pas qu'il s'agit d'un outil pratique pour le cas le plus courant : exécuter du code pour chaque élément. Dans les circonstances moins courantes où vous avez besoin de la position de l'élément, vous devez utiliser l'outil régulier moins pratique for
boucle.
Si vous disposez d'un compilateur prenant en charge C++14, vous pouvez le faire dans un style fonctionnel :
#include <iostream>
#include <string>
#include <vector>
#include <functional>
template<typename T>
void for_enum(T& container, std::function<void(int, typename T::value_type&)> op)
{
int idx = 0;
for(auto& value : container)
op(idx++, value);
}
int main()
{
std::vector<std::string> sv {"hi", "there"};
for_enum(sv, [](auto i, auto v) {
std::cout << i << " " << v << std::endl;
});
}
Fonctionne avec clang 3.4 et gcc 4.9 (pas avec 4.8) ; pour les deux, il est nécessaire de configurer -std=c++1y
. La raison pour laquelle vous avez besoin de c++14 est à cause de la auto
dans la fonction lambda.
J'ai lu dans vos commentaires qu'une des raisons pour lesquelles vous voulez connaître l'indice est de savoir si l'élément est le premier/dernier de la séquence. Si c'est le cas, vous pouvez faire
for(auto& elem:list) {
// loop code ...
if(&elem == &*std::begin(list)){ ... special code for first element ... }
if(&elem == &*std::prev(std::end(list))){ ... special code for last element ... }
// if(&elem == &*std::rbegin(list)){... (C++14 only) special code for last element ...}
// loop code ...
}
EDITAR: Par exemple, ceci imprime un conteneur en sautant un séparateur dans le dernier élément. Fonctionne pour la plupart des conteneurs que je peux imaginer (y compris les tableaux), (démonstration en ligne http://coliru.stacked-crooked.com/a/9bdce059abd87f91 ):
#include <iostream>
#include <vector>
#include <list>
#include <set>
using namespace std;
template<class Container>
void print(Container const& c){
for(auto& x:c){
std::cout << x;
if(&x != &*std::prev(std::end(c))) std::cout << ", "; // special code for last element
}
std::cout << std::endl;
}
int main() {
std::vector<double> v{1.,2.,3.};
print(v); // prints 1,2,3
std::list<double> l{1.,2.,3.};
print(l); // prints 1,2,3
std::initializer_list<double> i{1.,2.,3.};
print(i); // prints 1,2,3
std::set<double> s{1.,2.,3.};
print(s); // print 1,2,3
double a[3] = {1.,2.,3.}; // works for C-arrays as well
print(a); // print 1,2,3
}
Veuillez noter (avant tout downvoting injustifié) que l'auteur de la question pose cette question dans le contexte de la détection du dernier élément dans une boucle for-ranged pour un conteneur. Pour cela, je ne vois pas pourquoi la comparaison de &elem
y &*std::prev(std::end(list))
ne fonctionnera pas ou ne sera pas pratique. Je suis d'accord avec l'autre réponse qu'un for basé sur un itérateur est plus approprié pour cela, mais quand même.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.
17 votes
Ce n'est pas à cela que sert la gamme pour (heh, est-ce un jeu de mots ?)
2 votes
Ceci n'est pas possible dans les conteneurs STL, à moins d'utiliser
std::find
ou toute autre fonction excessive. On ne peut pas conclure des itérateurs à partir d'éléments contenus. Pourquoi ne pas maintenir un itérateur ?2 votes
Pour deux raisons. La première est que tout ce que je veux faire (dans ce cas) est de voir si je suis au dernier élément ou pas :) et la seconde est que le compilateur doit en maintenir une, pourquoi ne puis-je pas y accéder ? "this" est une variable dont la portée est maintenue par le compilateur, pourquoi pas ici ? Ou alors, proposez une syntaxe alternative (mais toujours pratique) qui, comme le fait javascript, définit une variable qui change au fur et à mesure que l'on avance dans la boucle. for(auto& index:liste)
1 votes
@FredFinkle vous avez en fait raison, il existe un itérateur mais lorsqu'on utilise une gamme basée sur
for
il s'agit d'un nom interne au compilateur et ne peut donc pas être utilisé dans votre code. Donc, si vous voulez vraiment savoir si vous êtes au dernier élément, vous devez utiliser la fonctionfor(;;)
boucle.0 votes
En rapport : https://stackoverflow.com/q/28769156/364696
0 votes
L'en-tête seulement cppitertools met en œuvre ce principe.