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

 C++ Discussion :

modification du fonctionnement des "range based for loop"


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Par défaut modification du fonctionnement des "range based for loop"
    Bonjour à tous,

    Pour des raisons de lisibilité et de convivialité, j'aime à utiliser les fameux "range based for loop". Cependant, dans certains cas, je souhaiterais pouvoir agir sur le fonctionnement interne de cette boucle.

    J'ai pu voir ça et là comment était interprété ce type de boucle (nécessité d'avoir un begin() et un end() comme pour une itération classique) mais je me demande si il existait un moyen de modifier le fonctionnement de ce type de boucle (comme on le fait pour un itérateur spécialisé).

    par exemple, le code suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    std::vector<int> mData{0, 1, 2, 3, 4, 5};
     
    for(auto & v:mData) {
    	if(v%2) {
    		std::swap(v, mData[mData.size()-1]);
    		mData.resize(mData.size()-1);
    	}
     
    	std::cout << v << ' ';
    }
    me retourne sans surprise (compte tenu du fonctionnement interne de la boucle for) le résultat :
    0 5 2 4 4 2
    or, on l'a compris, le résultat attendu est plutôt :
    0 4 2
    La réponse peut facilement être obtenu grâce au code suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    auto it = mData.begin();
    while(it != mData.end()) {
    	if(*it%2) {
    		std::swap(*it, mData[mData.size()-1]);
    		mData.resize(mData.size()-1);
    	} else {
    		std::cout << *it << '\n';
    		++it;
    	}
    }
    Qui nous donne bien le retour escompté.

    Mais ici, je perds complètement la convivialité du "range based for loop"... Dans un cas simpliste comme ce dernier, je m’accommode tout à fait de ce fonctionnement ; mais lorsque les containers sont plus "tricky" et nécessitent des iterator plus spécialisés, cela complexifie le code. Cette réflexion est aussi valable lorsque l'on se retrouve à avoir 15 boucles du même type mais légèrement différentes à écrire

    La question finale est donc : Comment puis-je modifier le fonctionnement interne du "Range based for loop" afin que ce dernier fonctionne sur la base d'un iterateur spécifique ?


    Merci d'avance !

  2. #2
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 760
    Par défaut
    Ce n'est pas la boucle qu'il faut modifier, mais le conteneur. Si on regarde rangev3 (ou les ranges de c++20), on peut faire quelque chose comme

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    auto pred = [](auto&& x){ return x%2; };
    // auto pred = fn::_ % 2; si on veut simplifier la syntaxe
    for(auto & v:mData | view::filter(pred)) {
    	std::cout << v << ' ';
    }
    Ce n'est pas exactement la même chose que ton exemple, mais le principe reste le même: wrapper le conteneur dans un objet compatible avec begin()/end() et qui retourne des itérateurs spécialisés.

    En C++17, il est possible d'avoir des types différents pour begin() et end(), ce qui permet de simplifier la création de tel filtre. Par exemple, on peut faire quelque chose comme
    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
    struct myfilter
    {
      std::vector<int>& v;
     
      struct senti_t {};
     
      struct iterator
      {
        std::vector<int>* v; // un itérateur ne doit pas prendre le conteneur sous forme de référence
                             // sinon it = it2 va écraser les valeurs du vector
        std::size_t i = 0;
     
        iterator& operator++()
        {
          while (++i != v->size() && (*v)[i] % 2) {
          }
          return *this;
        }
     
        bool operator!=(senti_t const&) const
        {
          return i != v->size();
        }
     
        int& operator*()
        {
          return (*v)[i];
        }
      };
     
      iterator begin() { return {&v}; }
      senti_t end() { return {}; }
    };
     
    #include <iostream>
     
    int main()
    {
      std::vector<int> v{0, 1, 2, 3, 4, 5};
      for (auto&& x : v) std::cout << x << ",";
      std::cout << "\n";
      for (auto&& x : myfilter{v}) std::cout << x << ",";
    }
    résultat:


  3. #3
    Membre éclairé Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Par défaut
    Bonjour et merci pour ce retour très précis.

    Donc si je comprends bien, un itérateur spécialisé fait l'affaire alors (c'est comme ça que j'appelle les itérateurs perso mais je ne sais pas s'il existe un terme plus courant...) ?
    Je ne m'était pas lancé dans ces tests pensant que le fonctionnement était fondamentalement différent...

    Je penserais à l'idée de séparer mes itérateurs (en faisant des filtres comme tu le présentes). Cela semble très pratique pour réaliser des itérations filtres différents selon les opérations que l'on souhaite faire sur ses conteneurs. Très bonne idée.

    Par contre, pour info, pourquoi la structure senti_t plutôt que le classique &v[v.size()] ? Avoir des types différents pour begin() et end() permet des applications particulières ou il s'agit simplement de la construction globale du système qui permet (impose) ça ?

    Quoi qu'il en soit, je vais essayer ça des demain. Comme ça, je pense que ça marchera, mais dans mon cas, le std::swap et le resize() auront leur importance dans mes applications futures ; le but n'étant pas uniquement d'utiliser les valeurs, mais avant tout de trier le conteneur.

    Merci encore !

  4. #4
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 760
    Par défaut
    Avant c++17, le langage impose des types identiques, maintenant la restriction est levée. Mais rien n'empêche d'avoir 2 itérateurs de même type. Tu peux voir l'équivalent avec une boucle classique sur la doc: https://en.cppreference.com/w/cpp/language/range-for

    En réalité, pouvoir mettre des types différents permet beaucoup plus de souplesse. Par exemple, comment faire l'équivalent d'un strchr avec 2 itérateurs de même type (chercher un caractère dans un char*) ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    iterator {
      char* p;
      char c;
    }
    It it{p, ???};
    It end(???, '/');
     
    it != end // quelle condition faire ?
    operator!=(iter a, iter b) { return *a.p != '\0' && *a.p != b.c } // Mais alors end != it ne fonctionnera pas
    Alors qu'avec des types différents, il suffit d'avoir le char* comme itérateur et une "sentinelle" comme fin (ce que j'ai nommé senti_t dans mon code). Actuellement, aucun conteneur de la STL ne prévoit cela, car incompatible avec les algorithmes qui imposent toujours un type identique pour first et last (ce qui n'est plus le cas en C++20).

    Et sinon, à la base je voulais faire un exemple avec la suppression des nombres impaires et comme la position de fin change la sentinelle me paraissait avoir plus de sens. Mais comme c'est un plus complexe et que je n'avais pas envie de me prendre la tête, j'ai changé d'avis en cours de route pour faire un simple filtre

  5. #5
    Invité
    Invité(e)
    Par défaut
    Bonjour,

    Citation Envoyé par BioKore Voir le message
    mais dans mon cas, le std::swap et le resize() auront leur importance dans mes applications futures ; le but n'étant pas uniquement d'utiliser les valeurs, mais avant tout de trier le conteneur.
    Mélanger parcours et modification d'un conteneur, c'est rarement une bonne idée. Tu as de bonnes chances d'invalider les itérateurs et te retrouver avec un UB (Undefined Behavior).

    Pourquoi ne pas utiliser l'algorithme fait pour ça ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    std::vector<int> mData{0, 1, 2, 3, 4, 5};
    mData.erase(std::remove_if(mData.begin(), mData.end(), [](auto v) { return v%2; }),
                mData.end());
    for(auto v : mData) {
    	std::cout << v << ' ';
    }

  6. #6
    Membre éclairé Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Par défaut
    Bon, je viens de faire des tests, et effectivement, dans mon cas, je rencontre encore des erreurs

    Si je n'utilise pas de std::remove_if c'est pour deux raisons. La première et principale raison est que je ne souhaite pas supprimer définitivement la donnée. Simplement la "désactiver" et la pousser hors du champ de vision de mes itérateurs. La seconde raison, beaucoup moins importante, je l'accorde, c'est la lisibilité du truc...

    Je pense qu'une explication plus en profondeur du l'utilisation finale s'impose. En gros, je suis en train de réaliser une sorte de free-list indexée, mais dans laquelle les éléments actifs sont toujours contigus. Mon vœux pieu est donc de pouvoir manipuler les objets de cette classe grâce aux itérateurs (et range base for loop) et sans fonction de "rangement". En tout temps, les objets actifs restent contigus. Pour info, et compte tenu du fait que j'utilise, pour le moment, des swap, on comprends aisément que l'ordre des éléments m'importe peu...

    Ce container est sensé devenir celui des composants de mon ECS.

    Selon moi, ceci devrais être réalisable de manière relativement simple ; je n'arrive pas à voir sur quelle partie je dois m’orienter.

    Ci-dessous, mon code de test :

    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
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
     
    #pragma once
     
    #include <iostream>
     
    #include <vector>
    #include <limits>
    #include <cassert>
     
    using index_t = std::size_t;
    static constexpr std::size_t invalid_index = std::numeric_limits<index_t>::max();
     
     
    struct IndexWrapper {
    	index_t objectID;
    };
     
    template<typename ObjectType>
    struct ObjectWrapper: public IndexWrapper, public ObjectType
    {
    	template<typename ...Args>
    	explicit ObjectWrapper(index_t const id, Args... args): IndexWrapper{id}, ObjectType{args...} {}
     
    	ObjectWrapper(): IndexWrapper{invalid_index} {}
    };
     
    template<typename ObjectType>
    class ObjectPool
    {
    	using data_t = ObjectWrapper<ObjectType>;
     
    private:
    	std::vector<data_t> mData;
    	std::vector<size_t> mIndex;
     
    	size_t indexOf(index_t const objectID) {
    		assert(exist(objectID));
    		return mIndex[objectID];
    	}
     
    public:
    	explicit ObjectPool(size_t const cap) {
    		mData.reserve(cap);
    		mIndex.resize(cap, invalid_index);
    	}
     
    	~ObjectPool() = default;
     
    	bool exist(index_t const objectID) {
    		return mIndex[objectID] != invalid_index;
    	}
     
    	data_t & operator[](index_t const objectID) {
    		return mData[indexOf(objectID)];
    	}
     
    	data_t const & operator[](index_t const objectID) const {
    		return const_cast<data_t&>(mData[indexOf(objectID)]);
    	}
     
    	data_t & get(index_t const objectID) {
    		return mData[indexOf(objectID)];
    	}
     
    	data_t const & get(index_t const objectID) const {
    		return const_cast<data_t&>(mData[indexOf(objectID)]);
    	}
     
    	template<typename ...Args>
    	void push(index_t const objectID, Args... args) {
    		mData.push_back(data_t{objectID, args...});
    		mIndex[objectID] = size();
    	}
     
    	void pop(index_t const objectID) {
    		assert(exist(objectID));
     
    		index_t tempID{mData[size()-1].objectID};
    		std::swap(mData[indexOf(objectID)], mData[size()-1]);
    		mIndex[tempID] = indexOf(objectID);
    		mIndex[objectID] = invalid_index;
     
    		mData.resize(size()-1);
    	}
     
     
    	class iterator
    	{
    	private:
    		std::vector<data_t> & mCurrent;
    		size_t mCursor{0};
    		size_t mRange{0};
     
    	public:
    		iterator(ObjectPool<ObjectType> & src):
    			mCurrent{src.mData},
    			mCursor{0},
    			mRange{src.size()}
    				{
     
    				}
     
    		iterator(ObjectPool<ObjectType> & src, size_t const offset):
    			mCurrent{src.mData},
    			mCursor{offset},
    			mRange{src.size()}
    				{
     
    				}
     
    		~iterator() = default;
     
    		void operator++() {
    			if(mRange != mCurrent.size()) {
    				mRange = mCurrent.size();
    			} else {
    				++mCursor;
    			}
    		}
     
    		bool operator!=(iterator const & src) const {
    			return mCursor != (src.mCursor - (src.mRange - mRange));
    		}
     
    		data_t & operator*() {
    			return mCurrent[mCursor];
    		}
    	};
     
     
     
    	size_t size() {
    		return mData.size();
    	}
     
    	iterator begin() {
    		iterator it(*this);
    		return it;
    	}
     
    	iterator end() {
    		iterator it(*this, size());
    		return it;
    	}
     
     
    };
     
     
     
    struct val{
    	uint32_t value{0};
    };
     
     
    int main() {
     
    	ObjectPool<val> mVal(6);
     
    	for(size_t i{0}; i < 6; ++i) {
    		mVal.push(i, i);
    	}
     
    	std::cout << "mVal size : " << mVal.size() << '\n';
     
    	for(auto & v:mVal) {
    		if(v.value % 2) {
    			std::cout << "delete : " << v.objectID << '\n';
    			mVal.pop(v.objectID);
    		} else {
    			std::cout << v.objectID << ':';
    			std::cout << v.value << '\n';
    		}
    	}
     
     
    	return 0;
    }
    Je dois encore le commenter ; j'espère donc qu'il est suffisamment lisible...

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

Discussions similaires

  1. Map, Set et range-based for
    Par darkman19320 dans le forum C++
    Réponses: 5
    Dernier message: 05/07/2017, 09h55
  2. Réponses: 0
    Dernier message: 18/11/2014, 20h15
  3. Traduction de "range based" for loop
    Par ram-0000 dans le forum Langage
    Réponses: 6
    Dernier message: 01/03/2013, 11h15
  4. Réponses: 6
    Dernier message: 15/11/2012, 11h53
  5. Réponses: 2
    Dernier message: 29/09/2005, 11h34

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