Outils pour utilisateurs

Outils du site


lang:cpp:boucles

Boucle sur des données (old style)

Conteneur supportant les index

std::array<int, 4> 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<int> vector{ 10, 20, 30, 40 };
 
// Lecture / écriture
std::vector<int>::iterator it;
for (it = vector.begin(); it != vector.end(); it++)
  (*it)++;
 
// Lecture seule
std::vector<int>::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<int, 4> 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<std::string> set { "10", "20", "30", "40" };
 
for (const std::string& val : set)
  std::cout << val << std::endl;

Conteneur multi-types (map, tuple, ...)

std::map<int, long> 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<int, long> map = std::map<int, long, std::less<int>, std::allocator<std::pair<const int, long> > >{std::initializer_list<std::pair<const int, long> >{std::pair<const int, long>{10, 10}, std::pair<const int, long>{20, 20}, std::pair<const int, long>{30, 30}, std::pair<const int, long>{40, 40}}, std::less<int>(), std::allocator<std::pair<const int, long> >()};
{
  std::map<int, long, std::less<int>, std::allocator<std::pair<const int, long> > > & __range1 = map;
  std::_Rb_tree_iterator<std::pair<const int, long> > __begin1 = __range1.begin();
  std::_Rb_tree_iterator<std::pair<const int, long> > __end1 = __range1.end();
  for(; __begin1.operator!=(__end1); __begin1.operator++()) 
  {
    const std::pair<const int, long> & __operator9 = __begin1.operator*();
    std::tuple_element<0, const std::pair<const int, long> >::type& key = std::get<0UL>(__operator9);
    std::tuple_element<1, const std::pair<const int, long> >::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. C++11 range-based for loops 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 <iostream>
 
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<const DataSample>(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 <ranges>
 
for (int i : std::views::iota(0, 100))
  std::cout << i << std::endl;
  • Pour faire la boucle à l'envers :
#include <ranges>
 
for (int i : std::views::iota(0, 100) | std::views::reverse)
  std::cout << i << std::endl;
  • S'arrêter sous une certaine condition :
#include <ranges>
 
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 <ranges>
 
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 <ranges>
 
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<int> 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 <ranges>
 
int main() {
  for (int i :
       std::views::iota(0, 100) |
       std::views::take(10))
    std::cout << i << std::endl;
}

Via une coroutine

#include <experimental/generator>
#include <iostream>
 
std::experimental::generator<int> 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;
  }
}
lang/cpp/boucles.txt · Dernière modification : 2020/06/16 15:00 de root