[Aller plus loin]Filtrer les données d'un conteneur pendant une itération via ranged-based for loop
par
, 09/09/2019 à 05h25 (823 Affichages)
Suite à mon précédent billet j'ai voulu pousser un peu pour arriver à une syntaxe telle que for (auto value : vec | [](...){...}) que je trouve plutôt attrayante. Et surtout ne pas avoir à taper le nom de la classe au complet, chose que je trouve des plus pénibles.
Ce fût étonnamment rapide puisque le simple ajout d'un opérateur libre permet d'y parvenir :Ce code compile sous VS 2017 à partir de C++14 (et possiblement C++11) et est accepté par Clang et g++ en C++11.
Code c++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 template<class Container> ForRangedLoopFilter<Container> operator|(Container& container, std::function<bool(const typename Container::value_type& entry)> filter) { return ForRangedLoopFilter<Container>(container, filter); }
Afin de réutiliser l'exemple du billet précédent, il convient d'ajouter le support pour un vector constant:
Code c++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 template<class Container> ForRangedLoopFilter<Container> operator|(const Container& container, std::function<bool(const typename Container::value_type& entry)> filter) { return ForRangedLoopFilter<Container>(container, filter); }
Maintenant, il est assez simple d'étoffer cette base pour pouvoir chaîner les filtres en modifiant légèrement la classe de filtrage et en ajoutant un opérateur| interne:Notez que je retourne une copie depuis l'opérateur | interne : c'est le seul moyen que j'ai trouvé pour faire fonctionner la syntaxe chaînée, sinon une copie est faîte et aucun filtre n'est appliqué...
Code c++ : 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 template<class Container> class ForRangedLoopFilter { using value_type = typename Container::value_type; using iterator = typename Container::iterator; public: friend class Iterator; class Iterator { ... private: iterator first() { mIterator = mOwner.mContainer.begin(); if (!isValid(mIterator)) mIterator = next(); return mIterator; } iterator next() { do { mIterator++; } while (mIterator != last() && !isValid(mIterator)); return mIterator; } ... bool isValid(const iterator& it) const { for (auto&& func : mOwner.mFilterFunctions) { if (!func(*it)) return false; } return true; } ... }; public: template<class FilterFunc> ForRangedLoopFilter(Container& container, FilterFunc func) : mContainer(container) { mFilterFunctions.push_back(func); } template<class FilterFunc> ForRangedLoopFilter(const Container& container, FilterFunc func) : mContainer(const_cast<Container&>(container)) { mFilterFunctions.push_back(func); } ... ForRangedLoopFilter<Container> operator|(std::function<bool(const value_type& entry)> filter) { mFilterFunctions.push_back(filter); return *this; } private: Container & mContainer; std::vector<std::function<bool(const value_type& entry)>> mFilterFunctions; };
Pour que le retour par référence fonctionne, il faudrait découper le code en 2 étapes :et alors les 2 filtres sont appliqués.
Code c++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 auto range = vec | [](int v) { return v % 2 == 0; } | [](int v) { return v % 3 == 0; }; for (int v : range)
Vu le faible poids d'un std::function et la facilité à les copier, je préfère faire une copie et permettre la syntaxe chaînée directement dans le for.
Le code complet:
Code c++ : 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 #include <functional> #include <vector> 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 (!isValid(mIterator)) mIterator = next(); return mIterator; } iterator next() { do { mIterator++; } while (mIterator != last() && !isValid(mIterator)); return mIterator; } iterator last() { return mOwner.mContainer.end(); } bool isValid(const iterator& it) const { for (auto&& func : mOwner.mFilterFunctions) { if (!func(*it)) return false; } return true; } private: ForRangedLoopFilter & mOwner; iterator mIterator; }; public: template<class FilterFunc> ForRangedLoopFilter(Container& container, FilterFunc func) : mContainer(container) { mFilterFunctions.push_back(func); } template<class FilterFunc> ForRangedLoopFilter(const Container& container, FilterFunc func) : mContainer(const_cast<Container&>(container)) { mFilterFunctions.push_back(func); } Iterator begin() { return Iterator(*this); } Iterator end() { return Iterator(*this, true); } ForRangedLoopFilter<Container> operator|(std::function<bool(const value_type& entry)> filter) { mFilterFunctions.push_back(filter); return *this; } private: Container & mContainer; std::vector<std::function<bool(const value_type& entry)>> mFilterFunctions; }; template<class Container> ForRangedLoopFilter<Container> operator|(Container& container, std::function<bool(const typename Container::value_type& entry)> filter) { return ForRangedLoopFilter<Container>(container, filter); } template<class Container> ForRangedLoopFilter<Container> operator|(const Container& container, std::function<bool(const typename Container::value_type& entry)> filter) { return ForRangedLoopFilter<Container>(const_cast<Container&>(container), filter); } #include <iostream> #include <vector> #include <string> int main() { std::vector<int> vec{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; for (int v : vec | [](int v) { return v % 2 == 0; } | [](int v) { return v % 3 == 0; }) { std::cout << v << std::endl; } struct Toto { int value; std::string nom; }; const std::vector<Toto> vec2{ { 0, "toto" },{ 1, "tata" },{ 2, "titi" } }; for (const Toto& v : vec2 | [](const Toto& v) { return v.value % 2 == 0; } | [](const Toto& v) { return v.nom.find('i') != std::string::npos; }) { std::cout << v.value << ":" << v.nom << std::endl; } return 0; }