IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

[C++11] Range based loop : conserver la position dans le tableau ?


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut [C++11] Range based loop : conserver la position dans le tableau ?
    Bonjour,

    Lorsque l'on utilise la nouvelle syntaxe pour les boucles en C++11, par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    std::vector<double> array(10);
    for (auto data : array)
    {
        // ...
    }
    ... alors dans ce cas, data est de type double. On peut accéder à la valeur, mais une fois dans la boucle, on ne connaît pas a priori sa position dans le std::vector. Une solution est d'itérer sois-même à côté :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    std::vector<double> array(10);
    size_t i = 0;
    for (auto data : array)
    {
        // ...
     
        ++i;
    }
    ... mais on perd l'intérêt de la nouvelle syntaxe.

    Du coup, j'ai implémenté moi-même quelque chose d'assez générique (j'ai condensé un peu le code pour que ça soit lisible sur le forum) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    template<class T>
    class pairs_proxy
    {
        T& obj;
     
    public :
     
        pairs_proxy(T& t) : obj(t) {}
     
        typedef typename T::iterator       proxy_iterator;
        typedef typename T::const_iterator proxy_const_iterator;
     
        class iterator
        {
            proxy_iterator iter;
     
            explicit iterator(proxy_iterator i) : iter(i) {}
     
        public :
     
            iterator() = default;
     
            iterator& operator ++ () {
                ++iter; return *this;
            }
     
            iterator operator ++ (int) {
                iterator tmp = *this; return ++tmp;
            }
     
            iterator& operator -- () {
                --iter; return *this;
            }
     
            iterator operator -- (int) {
                iterator tmp = *this; return --tmp;
            }
     
            proxy_iterator& operator * () {
                return iter;
            }
     
            bool operator == (const iterator& i) const {
                return iter == i.iter;
            }
            bool operator != (const iterator& i) const {
                return iter != i.iter;
            }
     
            friend class const_iterator;
            friend class pairs_proxy;
        };
     
        class const_iterator
        {
            proxy_const_iterator iter;
     
            explicit const_iterator(proxy_const_iterator i) : iter(i) {}
     
        public :
     
            const_iterator() = default;
     
            const_iterator(const iterator& i) : iter(i.iter) {}
     
            const_iterator& operator ++ () {
                ++iter; return *this;
            }
     
            const_iterator operator ++ (int) {
                const_iterator tmp = *this; return ++tmp;
            }
     
            const_iterator& operator -- () {
                --iter; return *this;
            }
     
            const_iterator operator -- (int) {
                const_iterator tmp = *this; return --tmp;
            }
     
            proxy_const_iterator& operator * () {
                return iter;
            }
     
            bool operator == (const const_iterator& i) const {
                return iter == i.iter;
            }
            bool operator != (const const_iterator& i) const {
                return iter != i.iter;
            }
     
            friend class pairs_proxy;
        };
     
        iterator begin() {
            return iterator(obj->begin());
        }
     
        iterator end() {
            return iterator(obj->end());
        }
     
        const_iterator begin() const {
            return const_iterator(obj->begin());
        }
     
        const_iterator end() const {
            return const_iterator(obj->end());
        }
    };
     
    template<class T>
    pairs_proxy<T> pairs(T& t) {
        return pairs_proxy<T>(t);
    }
    On l'utilise alors comme suit :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    std::vector<double> array(10);
    for (auto iter : pairs(array))
    {
        // ...
    }
    ... et c'est en réalité équivalent à l'itération "à l'ancienne" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    std::vector<double> array(10);
    for (auto iter = array.begin(); iter != array.end(); ++iter)
    {
        // ...
    }
    Ma question est alors : existe-t-il déjà quelque chose dans le standard pour faire ce que je souhaite ?
    Et si non, mon implémentation est-elle optimale à votre avis ? Que changeriez-vous ?

    PS : Pour l'anecdote, le mot "pairs" est inspiré de Lua :
    Code Lua : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    local array = {1, 2, 5, 0, -5, 5.2, 85};
    for idx, data in pairs(array) do
        // 'idx' est l'indice sur lequel on itère (automatiquement)
        // 'data' est la valeur de array[idx]
    end
    Si on n'utilise pas pairs dans l'exemple ci-dessus, alors on perd l'information sur idx, et on retrouve une syntaxe très proche du premier morceau de code en haut de ce message :
    Code Lua : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    local array = {1, 2, 5, 0, -5, 5.2, 85};
    for data in array do
        // ...
    end

  2. #2
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    boost::range (un très belle bibliothèque je trouve) :

    http://www.boost.org/doc/libs/1_50_0...e/indexed.html

    Pour l'implémentation, je n'ai pas regarder ton code, mais tu peux essayer de te lancer dans celui de boost::indexed_range pour comparer

  3. #3
    Rédacteur

    Avatar de Matthieu Brucher
    Profil pro
    Développeur HPC
    Inscrit en
    Juillet 2005
    Messages
    9 810
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France, Pyrénées Atlantiques (Aquitaine)

    Informations professionnelles :
    Activité : Développeur HPC
    Secteur : Industrie

    Informations forums :
    Inscription : Juillet 2005
    Messages : 9 810
    Par défaut
    Le côté "on perd la position" est un peu faux, on n'a pas en général la position dans un conteneur, sauf à compter soi-même. Donc pour moi le problème posé n'existe pas avec la nouvelle écriture

  4. #4
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut
    Citation Envoyé par Matthieu Brucher Voir le message
    Le côté "on perd la position" est un peu faux, on n'a pas en général la position dans un conteneur, sauf à compter soi-même. Donc pour moi le problème posé n'existe pas avec la nouvelle écriture
    En interne, les boucles for de ce type utilisent le concept d'itérateurs. Et les itérateurs gardent bien avec eux l'information sur "où trouver la donnée pointée" : ce sont soit de simples pointeurs qui en donnent l'adresse, soit un système plus compliqué mais qui contiendra toujours l'information sur la position de la donnée pointée au sein du conteneur (ce que je raconte est peut être limité aux conteneurs à accès aléatoire).

    Par exemple dans une std::map, l'itérateur est une paire clé/valeur, et la connaissance de la clé identifie sans ambiguïté la valeur. Pour un std::vector, iter - array.begin() te donnera toujours la position de la variable dans le tableau.

    Ce qui me chagrine, c'est qu'on incrémente déjà l'itérateur interne pour parcourir la boucle, mais que l'on a accès qu'à la valeur pointée par cet itérateur. Si on veut récupérer d'autres informations contenues dans l'itérateur (comme la position de la donnée), on doit refaire l'incrémentation sois-même à côté, et c'est du gâchis.

    Le problème se pose donc bien. Par exemple : comment créer un tableau de taille N dont les valeurs sont égales à leur indice dans le tableau en n'utilisant qu'une seule incrémentation par itération et avec la nouvelle syntaxe du C++11 ? Une des réponses consiste à utiliser la classe que j'ai introduite :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    std::vector array(N);
    for (auto iter : pairs(array))
        *iter = iter - array.begin();
    ... et une autre à utiliser boost::indexed (qui fait bel et bien ce que je demande, mais c'est boost, donc pas (encore?) standard ) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    std::vector array(N);
    for (auto iter : array | boost::adaptors::indexed(0))
        *iter = iter.index();

  5. #5
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Non les ranges (en tant que concept) ne sont pas encore standard, on reste uniquement sur la notion d'itérateur. Mais, personnelement, je ne vais pas me priver d'utiliser un tel outil juste parce que c'est pas dans la bibliothèque standard.

    La notion de "position" dans un conteneur n'existe pas nécessairement, elle n'existe que quand tu lui imposes. En soi un conteneur n'a pas besoin d'être indexé, il a juste besoin d'être parcouru, et c'est cet/ces ordres de parcours qui peuvent définir une indexation.

    Les itérateur ne contiennent pas de "position" (au sens indice), il font juste référence à l'élément. Si tu veux une boucle for range-based avec des références alors utilise auto&.

    Les itérateurs peuvent définir une indexation par rapport à un autre itérateur grâce à la notion de distance entre itérateur, mais pas parcequ'ils contiennent la position, et ainsi cet indexation est propre à la facon dont tes itérateurs parcourent le conteneur. Si ton conteneur est continue en mémoire, alors tu peux faire de même avec auto& et l'arithmétique des pointeurs.

    Quand je regarde la facon dont un for range-based doit se comporter, j'ai bien l'impression qu'il sert à parcourir l'ensemble des données selon un ordre "par défaut" (définit par begin/end) : ie travailler sur un ensemble de données sans se préoccuper de l'ordre. Du coup l'utilisation même d'un for range-based semble être indiqué lorsque l'ordre de parcours n'importe pas : à partir de là, pourquoi te donnerait-il alors une information qui est sensé ne pas être utile ?

    A partir de là soit tu tiens à utiliser cette structure et tu rajoutes donc une indexation à l'ensemble des données (à la manière de boost::range), soit tu utilises un boucle for "classique" avec une pair d'itérateur pour utiliser l'indexation relative qu'ils permettent d'obtenir.

  6. #6
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut
    Citation Envoyé par =Flob90 Voir le message
    La notion de "position" dans un conteneur n'existe pas nécessairement, elle n'existe que quand tu lui imposes. En soi un conteneur n'a pas besoin d'être indexé, il a juste besoin d'être parcouru, et c'est cet/ces ordres de parcours qui peuvent définir une indexation.
    Exact, et c'est pourquoi j'ai précisé dans mon post précédent que cette discussion n'a probablement de sens que pour des conteneurs à accès aléatoire.

    Citation Envoyé par Flob90 Voir le message
    Les itérateur ne contiennent pas de "position" (au sens indice), il font juste référence à l'élément. Si tu veux une boucle for range-based avec des références alors utilise auto&.
    Je dirais que ça dépend de l'implémentation. Mais le fait est que ton itérateur, tu es sensé pouvoir l'incrémenter : il contient donc l'information nécessaire pour déterminer l'élément suivant. À mon sens, c'est une information de "position" (car on peut définir la "distance" par rapport à l'itérateur begin() comme étant la position, par exemple).

    Citation Envoyé par Flob90 Voir le message
    A partir de là soit tu tiens à utiliser cette structure et tu rajoutes donc une indexation à l'ensemble des données (à la manière de boost::range), soit tu utilises un boucle for "classique" avec une pair d'itérateur pour utiliser l'indexation relative qu'ils permettent d'obtenir.
    Tout à fait. Mais il est clair que la nouvelle syntaxe introduite par le C++11 permet aussi d'avoir un code plus concis, donc moins sujet aux erreurs et plus lisible (cf. l'exemple d'utilisation que je donne de mon code, comparé à la boucle "classique"). C'est en grande partie pour ça qu'elle m'intéresse.
    Si on peut avoir les mêmes fonctionnalités et les mêmes performances (a priori, mais c'est à vérifier) avec deux fois moins de code, pourquoi s'en priver ?

    Citation Envoyé par Flob90 Voir le message
    Mais, personnellement, je ne vais pas me priver d'utiliser un tel outil juste parce que c'est pas dans la bibliothèque standard.
    Bien sûr, moi non plus. Mais boost est tellement pénible à gérer, pour une si petite addition je préfère le faire moi même (surtout quand c'est implémenté en quelques lignes, comme dans le cas présent).

    S'il existe déjà quelque chose de standard qui permette de le faire, alors c'est encore mieux. Mais ça ne semble pas encore être le cas aujourd'hui, donc j'ai la réponse à ma question. Merci à vous

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 2
    Dernier message: 27/05/2014, 15h34
  2. lire d'une base de données et stocker dans un tableau
    Par michael_MS dans le forum ASP.NET
    Réponses: 23
    Dernier message: 21/11/2008, 15h00
  3. Position dans un tableau.
    Par dahmane2007 dans le forum VB.NET
    Réponses: 5
    Dernier message: 24/09/2008, 19h29
  4. Détermination d'une position dans un tableau
    Par oLie dans le forum Bibliothèque standard
    Réponses: 13
    Dernier message: 21/05/2008, 10h38
  5. [Base Access] Stocker une Table dans un tableau
    Par La Praline dans le forum VB 6 et antérieur
    Réponses: 78
    Dernier message: 25/04/2007, 15h24

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo