Ce soir, je m'ennuyais un peu. Et comme je dois faire du Python ces temps-ci pour une raison ou une autre et que je fais du C++ assez couramment, je me suis demandé comment faire des
générateurs de manières en C++, mécanisme très présent et très élégant dans Python.
Pour parler franchement, je n'ai pas réussi à les faire de manière "générique" et transparente va-t-on dire ; j'arrivais toujours à des syntaxes assez immondes et peu compréhensibles. Donc plutôt que de m'attaquer à cette tâche un peu compliquée, je me suis dit que reprendre la fonction xrange de Python 2.x (range dans Python 3.x) qui est peut-être le générateur le plus utilisé du langage
Je le rappelle pour ceux qui ne le savent pas, le principe d'un générateur est de calculer les valeurs dont on a besoin et de les renvoyer au fur et à mesure, plutôt que de tout calculer et renvoyer en une fois. On appelle ce mécanisme "évaluation paresseuse".
Afin d'avoir une syntaxe plus classe et plus Python, j'ai usé des petits avantages donnés par la nouvelle norme C++11, comme par exemple la nouvelle boucle for, directement piquée chez Java. Le but était d'obtenir la syntaxe suivante :
- Code:
-
#include <iostream>
int main()
{
for (int& i: range(100))
{
std::cout << i << std::endl;
}
}
Donc, quand on connaît un peu le C++11, on sait que cette boucle se ramène à la syntaxe suivante :
- Code:
-
#include <iostream>
int main()
{
auto x = range(100);
for (auto it = x.begin() ; it != x.end() ; ++it)
{
int i = *it;
std::cout << i << std::endl;
}
}
Ceci oblige à créer une classe range avec les fonctions suivantes :
- range(int)
- begin()
- end()
- operator++()
- operator!=()
- operator*()
Techniquement, ce qui va suivre est complètement dégueulasse et n'importe quel professeur ou personne un minimum sérieuse vous assassinerait probablement en voyant ça, mais toujours est-il que ça marche et que ça a bien le comportement de la fonction xrange() de Python. Voici donc l'implémentation C++ du générateur range, simplifié à l'extrême :
- Code:
-
class range
{
private:
const int _val; // Valeur finale
int _i; // Valeur courante
public:
range(int val):
_val(val),
_i(0)
{}
range& begin()
{
return *this;
}
range& end()
{
return *this;
}
bool operator!=(const range&)
{
return _i != _val;
}
void operator++()
{
++_i;
}
int operator*()
{
return _i;
}
};
Comme vous pouvez le voir, rien de bien compliqué. Seulement, on notera que l'objet en lui-même agit à la manière d'un itérateur, comme ceux de la bibliothèque standard. Notez aussi, qu'on n'a absolument rien à faire des valeurs de end() dans l'opérateur de différence et qu'on lui fait donc toujours retourner la même chose que begin(). Comment ça c'est moche ?
Quitte à être sale, autant rajouter une petite macro pour avoir un résultat encore plus Python, non ?
- Code:
-
#define in :
J'ai dû vous exposer un plus gros pourcentage de code C++ sale en quelques lignes que vous n'aurez l'occasion d'en voir tant que vous n'utiliserez pas de templates pour faire de la métaprogrammation comme des barbares. Mais quel est donc le plaisir quand enfin on peut écrire ceci de manière complètement légale et fonctionnelle^^
- Code:
-
int main()
{
for (int& i in range(100))
{
std::cout << i << std::endl;
if (i == 30) break;
}
}