=====Boucle sur des données (old style)=====
====Conteneur supportant les index====
std::array arr { 10, 20, 30, 40 };
for (size_t i = 0; i < arr.size(); i++)
std::cout << arr[i] << std::endl;
====Conteneur supportant les itérateurs====
std::vector vector{ 10, 20, 30, 40 };
// Lecture / écriture
std::vector::iterator it;
for (it = vector.begin(); it != vector.end(); it++)
(*it)++;
// Lecture seule
std::vector::const_iterator itc;
for (itc = vector.cbegin(); itc != vector.cend(); itc++)
std::cout << *itc << std::endl;
=====Programmation fonctionnelle=====
====for each====
===Conteneur de type primitif===
std::array arr = { 10, 20, 30, 40 };
// Ici, on utilise const & pour conserver le style fonctionnel.
// Mais passer en copie un type primitif n'est pas plus penalisant que d'utiliser une référence.
for (const int& i : arr)
std::cout << i << std::endl;
===Conteneur de type non primitif===
std::set set { "10", "20", "30", "40" };
for (const std::string& val : set)
std::cout << val << std::endl;
===Conteneur multi-types (map, tuple, ...)===
std::map map { {10, 10}, {20, 20}, {30, 30}, {40, 40} };
// L'utilisation de auto est obligatoire.
// const s'applique sur toutes les variables.
for (const auto & [key, value] : map)
std::cout << value << std::endl;
L'interprétation par le compilateur sera :
std::map map = std::map, std::allocator > >{std::initializer_list >{std::pair{10, 10}, std::pair{20, 20}, std::pair{30, 30}, std::pair{40, 40}}, std::less(), std::allocator >()};
{
std::map, std::allocator > > & __range1 = map;
std::_Rb_tree_iterator > __begin1 = __range1.begin();
std::_Rb_tree_iterator > __end1 = __range1.end();
for(; __begin1.operator!=(__end1); __begin1.operator++())
{
const std::pair & __operator9 = __begin1.operator*();
std::tuple_element<0, const std::pair >::type& key = std::get<0UL>(__operator9);
std::tuple_element<1, const std::pair >::type& value = std::get<1UL>(__operator9);
std::cout.operator<<(value).operator<<(std::endl);
}
}
===Implémentation sur une classe personnalisée===
Il faut définir l'itérateur et la classe à parcourir. [[https://www.cprogramming.com/c++11/c++11-ranged-for-loop.html|C++11 range-based for loops]] {{ :lang:cpp:boucles:range-based_for_loops_in_c_11_-_cprogramming.com_2019-12-26_12_35_03_.html |Archive le 26/12/2019}}
Dans l'idéal, ''DataSample'' devrait prendre une classe en ''template'' plutôt que la classe ''Data'' en dur.
Il faut commencer par déclarer l'itérateur.
#include
class DataSample;
// Données accessibles depuis la boucle.
class Data
{
public:
int getA() const { return a; }
void setA(int aa) { a = aa; }
private:
int a;
};
// Itérateur accessible en écriture.
class Iter
{
public:
Iter(DataSample &p_vec, int pos) : _pos(pos), _p_vec(p_vec) {}
bool operator!=(const Iter &other) const { return _pos != other._pos; }
Data &operator*();
Iter &operator++()
{
++_pos;
return *this;
}
private:
int _pos;
DataSample &_p_vec;
};
// Iterateur accessible uniquement en lecture.
class ConstIter
{
public:
ConstIter(const DataSample &p_vec, int pos) : _pos(pos), _p_vec(p_vec) {}
bool operator!=(const ConstIter &other) const { return _pos != other._pos; }
const Data &operator*() const;
const ConstIter &operator++()
{
++_pos;
return *this;
}
private:
int _pos;
const DataSample &_p_vec;
};
// Classe stockant les données et implémentant le pattern for each.
class DataSample
{
public:
// Méthodes accessibles en écriture.
Data &get(int col) { return _data[col]; }
Iter begin() { return Iter(*this, 0); }
Iter end() { return Iter(*this, 100); }
// Méthodes accessibles uniquement en lecture seule.
const Data &get(int col) const { return _data[col]; }
ConstIter begin() const { return ConstIter(*this, 0); }
ConstIter end() const { return ConstIter(*this, 100); }
void set(int index, int val) { _data[index].setA(val); }
private:
// Dans cette classe, les données sont sous forme d'un tableau.
Data _data[100];
};
Data &Iter::operator*() { return _p_vec.get(_pos); }
const Data &ConstIter::operator*() const { return _p_vec.get(_pos); }
// Exemple d'usage.
int main()
{
DataSample v;
for (int i = 0; i < 100; i++)
{
v.set(i, i);
}
// Utilisation de begin et end en non const.
// Le type de retour est Data& (qui peut être casté en const Data&).
for (Data &i : v)
{
std::cout << i.getA() << std::endl;
}
// Utilisation de begin et end en const.
// Le type de retour est obligatoirement const Data&.
for (const Data &i : static_cast(v))
{
std::cout << i.getA() << std::endl;
}
}
====Ranges====
Les ''ranges'' de la ''std'' n'implémentent que des algorithmes en $O(1)$. Il n'est donc pas possible de trier des vues.
* Boucle
#include
for (int i : std::views::iota(0, 100))
std::cout << i << std::endl;
* Pour faire la boucle à l'envers :
#include
for (int i : std::views::iota(0, 100) | std::views::reverse)
std::cout << i << std::endl;
* S'arrêter sous une certaine condition :
#include
for (int i : std::views::iota(0, 100) | std::views::take_while([](int i){ return 0 <= i && i <= 15; }))
std::cout << i << std::endl;
* Filter les valeurs :
#include
for (int i : std::views::iota(0, 100) | std::views::filter([](int i){ return 50 <= i && i <= 66; }))
std::cout << i << std::endl;
* Modifier les valeurs dans la vue :
#include
int main() {
for (const std::string& i :
// Integer
std::views::iota(0, 100) |
// const char *
std::views::transform([](int i) {
if (i % 15 == 0)
return "FooBar\n";
else if (i % 3 == 0)
return "Foo\n";
else if (i % 5 == 0)
return "Bar\n";
else
return "";
}))
std::cout << i << std::endl;
}
Les vues sont en lecture seule
Le code
int main() {
std::vector v {1, 2, 3, 4, 5, 6, 7, 8, 9};
for (int & i :
v |
std::views::take(5))
i = 3;
for (const int & i : v)
std::cout << i << "\n";
}
affichera
1
2
3
4
5
6
7
8
9
* Ne prendre que les premiers résultats
#include
int main() {
for (int i :
std::views::iota(0, 100) |
std::views::take(10))
std::cout << i << std::endl;
}
=====Via une coroutine=====
#include
#include
std::experimental::generator loop(int iterations)
{
for (int i = 0; i < iterations; i++)
{
co_yield i;
}
}
int main()
{
for (int i : loop(100))
{
std::cout << i << std::endl;
}
}