Developpez.com - Rubrique C++

Le Club des Développeurs et IT Pro

C++ : apprendre à filtrer les données d'un conteneur pendant une itération via « ranged-based for loop »

Un billet de Bousk

Le 18/08/2019, par Bousk, Rédacteur/Modérateur
Les ranged-base for loop apparues en C++11 sont un excellent moyen d'itérer sur l'ensemble des données d'un conteneur.
Mais si l'on veut ne traiter que certaines entrées selon un critère/filtre, il faut alors ajouter le filtre dans la boucle afin de ne pas exécuter l'opération sur cette entrée, ce qui n'est pas très élégant.
En attendant une solution du langage, possiblement via les ranged, dans une version future, voici un moyen relativement simple de filtrer dans la boucle, en C++11 uniquement :
Code c++ :
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
#include <functional> 
template<class Container> 
class ForRangedLoopFilter 
{ 
    using value_type = typename Container::value_type; 
    using iterator = typename Container::iterator; 
  
public: 
    friend class Iterator; 
    class Iterator 
    { 
    public: 
        Iterator(ForRangedLoopFilter& owner) 
            : mOwner(owner) 
        { 
            mIterator = first(); 
        } 
        Iterator(ForRangedLoopFilter& owner, bool) 
            : mOwner(owner) 
        { 
            mIterator = last(); 
        } 
  
        value_type& operator*() { return *mIterator; } 
        value_type& operator->() { return *mIterator; } 
  
        Iterator& operator++() { mIterator = next(); return *this; } 
  
        bool operator !=(const Iterator& other) const { return mIterator != other.mIterator; } 
  
    private: 
        iterator first() 
        { 
            mIterator = mOwner.mContainer.begin(); 
            if (!mOwner.mFilterFunction(*mIterator)) 
                mIterator = next(); 
            return mIterator; 
        } 
        iterator next() 
        { 
            do { 
                mIterator++; 
            } while (mIterator != last() && !mOwner.mFilterFunction(*mIterator)); 
            return mIterator; 
        } 
        iterator last() { return mOwner.mContainer.end(); } 
  
    private: 
        ForRangedLoopFilter& mOwner; 
        iterator mIterator; 
    }; 
  
public: 
    template<class FilterFunc> 
    ForRangedLoopFilter(Container& container, FilterFunc func) 
        : mContainer(container) 
        , mFilterFunction(func) 
    {} 
  
    Iterator begin() { return Iterator(*this); } 
    Iterator end() { return Iterator(*this, true); } 
  
private: 
    Container& mContainer; 
    std::function<bool(const value_type& entry)> mFilterFunction; 
};
Le code n'est certainement pas parfait mais vous donne une idée des possibilités. Prenez ceci comme une base.

Voici un exemple d'utilisation, pour afficher les nombres pairs sur [0,9] :
Code c++ :
1
2
3
4
5
6
7
8
9
10
11
#include <iostream> 
#include <vector> 
int main() 
{ 
    std::vector<int> vec{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 
    for (int v : ForRangedLoopFilter<std::vector<int>>(vec, [](int v) { return v%2 == 0;})) 
    { 
        std::cout << v << std::endl; 
    } 
    return 0; 
}
Pouvoir indiquer le filtre dans la déclaration de la boucle rend le code plus clair sur son but et utilisation selon moi.

Afin d'être utilisé avec un conteneur constant, il suffit d'ajouter un constructeur compatible :
Code c++ :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <functional> 
template<class Container> 
class ForRangedLoopFilter 
{ 
... 
public: 
... 
    template<class FilterFunc> 
    ForRangedLoopFilter(const Container& container, FilterFunc func) 
        : mContainer(const_cast<Container&>(container)) 
        , mFilterFunction(func) 
    {} 
... 
};
Je garde la référence interne non constante afin de simplifier le code, je ne fais aucune modification du conteneur donc il n'y a pas de soucis.

Code c++ :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream> 
#include <string> 
#include <vector> 
int main() 
{ 
    struct Toto { 
        int value; 
        std::string nom; 
    }; 
    const std::vector<Toto> vec{ {0, "toto"}, {1, "tata"}, {2, "titi"} }; 
    for (const Toto& v : ForRangedLoopFilter< std::vector<Toto>>(vec, [](const Toto& v) { return v.value % 2 == 0; })) 
    { 
        std::cout << v.value << ":" << v.nom << std::endl; 
    } 
    return 0; 
}
  Billet blog