web-dev-qa-db-fra.com

Trouver la position de l'élément dans C++ 11 en fonction de la boucle for?

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 conserver un itérateur séparé?

69
Fred Finkle

Oui tu peux, ça prend juste un peu de massage;)

L'astuce consiste à utiliser la composition: au lieu de parcourir le conteneur directement, vous le "compressez" avec un index le long du chemin.

Code de fermeture éclair spécialisé:

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 en l'utilisant:

#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 ideone , bien qu'il manque le support de la boucle for range, il est donc moins joli.

MODIFIER:

Je viens juste de me rappeler que je devrais consulter Boost.Range plus souvent. Malheureusement, aucune plage Zip, mais j’ai trouvé un Perl: boost::adaptors::indexed . Cependant, il faut avoir accès à l'itérateur pour extraire l'index. Honte: x

Sinon, avec le counting_range et un Zip générique, je suis sûr qu'il pourrait être 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 sous la forme d’une série de tuples de références et iota(0), créant simplement une plage "fausse" commençant à partir de 0 et ne comptant que vers l’infini (ou le maximum de son type ...).

59
Matthieu M.

jrok a raison: les boucles basées sur la plage ne sont pas conçues à cette fin.

Cependant, dans votre cas, il est possible de le calculer en utilisant l'arithmétique de pointeur 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 ceci est clairement une mauvaise pratique car il obscurcit le code et le rend plus fragile (il casse facilement si quelqu'un change le type de conteneur, surcharge l'opérateur & ou remplace 'auto &' par 'auto'. Bonne chance pour le déboguer!)

REMARQUE: La contiguïté est garantie pour le vecteur en C++ 03 et pour les tableaux et les chaînes en norme C++ 11.

26

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 plage pour. Rappelez-vous qu'il ne s'agit que d'un outil pratique pour le cas le plus courant: exécutez 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 la boucle for régulière moins pratique.

18
Nicol Bolas

Si vous avez un compilateur avec support 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 besoin de définir -std=c++1y. Vous avez besoin de c ++ 14 à cause des paramètres auto de la fonction lambda.

10
Michael

Basé sur la réponse de @Matthieu, il existe une solution très élégante utilisant le boost :: adapters :: indexed :

std::vector<std::string> strings{10, "Hello"};
int main(){
    strings[5] = "World";
    for(auto const& el: strings| boost::adaptors::indexed(0))
      std::cout << el.index() << ": " << el.value() << std::endl;
}

Tu peux l'essayer

Cela fonctionne assez bien comme "la solution du monde idéal" mentionné, a une jolie syntaxe et est concis. Notez que le type de el dans ce cas est quelque chose comme boost::foobar<const std::string&, int>, donc il gère la référence à cet endroit et aucune copie n’est effectuée. Il est même incroyablement efficace: https://godbolt.org/g/e4LMnJ (le code équivaut à conserver une propre variable de compteur qui est aussi bonne qu’elle obtient)

Pour être complet les alternatives:

size_t i = 0;
for(auto const& el: strings) {
  std::cout << i << ": " << el << std::endl;
  ++i;
}

Ou en utilisant la propriété contiguë d'un vecteur:

for(auto const& el: strings) {
  size_t i = &el - &strings.front();
  std::cout << i << ": " << el << std::endl;
}

La première génère le même code que la version de l'adaptateur de démarrage (optimal) et la dernière est 1 instruction plus longue: https://godbolt.org/g/nEG8f9

Note: Si vous voulez seulement savoir, si vous avez le dernier élément, vous pouvez utiliser:

for(auto const& el: strings) {
  bool isLast = &el == &strings.back();
  std::cout << isLast << ": " << el << std::endl;
}

Cela fonctionne pour tous les conteneurs standard, mais auto&/auto const& doit être utilisé (comme ci-dessus), mais cela est néanmoins recommandé. En fonction de l'entrée, cela peut aussi être assez rapide (surtout lorsque le compilateur connaît la taille de votre vecteur)

Remplacez le &foo par std::addressof(foo) pour être sûr pour le code générique.

9
Flamefire

Si vous insistez pour utiliser une valeur basée sur une plage et connaître un index, il est assez simple de maintenir l'indice comme indiqué ci-dessous .. Je ne pense pas qu'il existe une solution plus simple/plus propre pour les boucles basées sur la plage. Mais vraiment, pourquoi ne pas utiliser une norme pour (;;)? Cela rendrait probablement votre intention et votre code les plus clairs.

vector<int> list;
int idx = 0;
for(auto& elem:list) {
    int i = elem;
    //TODO whatever made you want the idx
    ++idx;
}
4
Jens Winslow

Il y a un moyen étonnamment simple de faire cela

vector<int> list;
for(auto& elem:list) {
    int i = (&elem-&*(list.begin()));
}

i sera votre index requis.

Cela tire parti du fait que les vecteurs C++ sont toujours contigus .

3
PulseJet

J'ai lu dans vos commentaires que l'une des raisons pour lesquelles vous voulez connaître l'index est de savoir si l'élément est le premier/dernier de la séquence. Si oui, 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 ... 
}

EDIT: Par exemple, ceci affiche 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
}
2
alfC

Tobias Widlund a écrit un en-tête de style Python sous licence Nice MIT uniquement énumérer (si C 17 17): 

GitHub

Article de blog

Vraiment sympa à utiliser:

std::vector<int> my_vector {1,3,3,7};

for(auto [i, my_element] : en::enumerate(my_vector))
{
    // do stuff
}
0
M. Ahnen

Voici une solution basée sur des macros qui surpasse la plupart des autres sur la simplicité, le temps de compilation et la qualité de génération de code:

#include <iostream>

#define fori(i, ...) if(size_t i = -1) for(__VA_ARGS__) if(i++, true)

int main() {
    fori(i, auto const & x : {"hello", "world", "!"}) {
        std::cout << i << " " << x << std::endl;
    }
}

Résultat:

$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate 
0 hello
1 world
2 !
0
Forrest Voight

Si vous voulez éviter de devoir écrire une fonction auxiliaire alors que la variable d'index est locale à la boucle, vous pouvez utiliser un lambda avec une variable mutable:

int main() {
    std::vector<char> values = {'a', 'b', 'c'};
    std::for_each(begin(values), end(values), [i = size_t{}] (auto x) mutable {
        std::cout << i << ' ' << x << '\n';
        ++i;
    });
}
0
Peleg Harel