J'ai un exemple de code Python que je dois imiter en C ++. Je n'ai besoin d'aucune solution spécifique (comme des solutions de rendement basées sur la co-routine, bien qu'elles soient également des réponses acceptables), j'ai simplement besoin de reproduire la sémantique d'une manière ou d'une autre.
Python
Il s'agit d'un générateur de séquence basique, clairement trop volumineux pour stocker une version matérialisée.
def pair_sequence():
for i in range(2**32):
for j in range(2**32):
yield (i, j)
Le but est de conserver deux instances de la séquence ci-dessus et de les parcourir en semi-lockstep, mais en morceaux. Dans l'exemple ci-dessous, first_pass
utilise la séquence de paires pour initialiser le tampon, et second_pass
régénère la même séquence exacte et traite à nouveau le tampon.
def run():
seq1 = pair_sequence()
seq2 = pair_sequence()
buffer = [0] * 1000
first_pass(seq1, buffer)
second_pass(seq2, buffer)
... repeat ...
C ++
La seule chose que je peux trouver pour une solution en C ++ est d'imiter yield
avec les coroutines C ++, mais je n'ai trouvé aucune bonne référence sur la façon de procéder. Je suis également intéressé par des solutions alternatives (non générales) pour ce problème. Je n'ai pas assez de budget mémoire pour conserver une copie de la séquence entre les passes.
Réponses:
Les générateurs existent en C ++, juste sous un autre nom: Input Iterators . Par exemple, lire à partir de
std::cin
est similaire à avoir un générateur dechar
.Vous devez simplement comprendre ce que fait un générateur:
Dans votre exemple trivial, c'est assez simple. Conceptuellement:
Bien sûr, nous emballons ceci comme une classe appropriée:
Alors hum ouais ... peut-être que C ++ est un peu plus bavard :)
la source
En C ++, il y a des itérateurs, mais implémenter un itérateur n'est pas simple: il faut consulter les concepts d'itérateur et concevoir soigneusement la nouvelle classe d'itérateur pour les implémenter. Heureusement, Boost a un modèle iterator_facade qui devrait aider à implémenter les itérateurs et les générateurs compatibles avec les itérateurs.
Parfois, une coroutine sans pile peut être utilisée pour implémenter un itérateur .
PS Voir aussi cet article qui évoque à la fois un
switch
hack de Christopher M. Kohlhoff et Boost.Coroutine d'Oliver Kowalke. Le travail d'Oliver Kowalke fait suite à Boost , Coroutine de Giovanni P. Deretta.PS je pense que vous pouvez aussi écrire une sorte de générateur avec des lambdas :
Ou avec un foncteur:
PS Voici un générateur implémenté avec les coroutines Mordor :
la source
Puisque Boost.Coroutine2 le supporte maintenant très bien (je l'ai trouvé parce que je voulais résoudre exactement le même
yield
problème), je poste le code C ++ qui correspond à votre intention initiale:Dans cet exemple,
pair_sequence
ne prend pas d'arguments supplémentaires. S'il le faut,std::bind
ou un lambda doit être utilisé pour générer un objet fonction qui ne prend qu'un seul argument (depush_type
), lorsqu'il est passé aucoro_t::pull_type
constructeur.la source
Toutes les réponses qui impliquent l'écriture de votre propre itérateur sont complètement fausses. De telles réponses passent totalement à côté de l'intérêt des générateurs Python (l'une des fonctionnalités les plus importantes et uniques du langage). La chose la plus importante à propos des générateurs est que l'exécution reprend là où elle s'était arrêtée. Cela n'arrive pas aux itérateurs. Au lieu de cela, vous devez stocker manuellement les informations d'état de sorte que lorsque l'opérateur ++ ou l'opérateur * est appelé à nouveau, les bonnes informations sont en place au tout début de l'appel de fonction suivant. C'est pourquoi écrire votre propre itérateur C ++ est une tâche gigantesque; tandis que les générateurs sont élégants et faciles à lire et à écrire.
Je ne pense pas qu'il existe un bon analogue pour les générateurs Python en C ++ natif, du moins pas encore (il y a un rummor qui aboutira à C ++ 17 ). Vous pouvez obtenir quelque chose de similaire en recourant à un tiers (par exemple la suggestion Boost de Yongwei) ou en utilisant le vôtre.
Je dirais que ce qui se rapproche le plus en C ++ natif, ce sont les threads. Un thread peut maintenir un ensemble suspendu de variables locales et continuer l'exécution là où il s'est arrêté, tout comme les générateurs, mais vous devez déployer un peu d'infrastructure supplémentaire pour prendre en charge la communication entre l'objet générateur et son appelant. Par exemple
Cette solution présente cependant plusieurs inconvénients:
la source
Vous devriez probablement vérifier les générateurs dans std :: experimental dans Visual Studio 2015, par exemple: https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/
Je pense que c'est exactement ce que vous recherchez. Les générateurs globaux devraient être disponibles en C ++ 17 car il ne s'agit que d'une fonctionnalité expérimentale de Microsoft VC.
la source
Si vous n'avez besoin de le faire que pour un nombre relativement petit de générateurs spécifiques, vous pouvez implémenter chacun en tant que classe, où les données membres sont équivalentes aux variables locales de la fonction de générateur Python. Ensuite, vous avez une fonction suivante qui renvoie la prochaine chose que le générateur produirait, mettant à jour l'état interne comme il le fait.
C'est fondamentalement similaire à la façon dont les générateurs Python sont implémentés, je crois. La principale différence étant qu'ils peuvent se souvenir d'un décalage dans le bytecode pour la fonction de générateur dans le cadre de "l'état interne", ce qui signifie que les générateurs peuvent être écrits comme des boucles contenant des rendements. Vous devrez à la place calculer la valeur suivante à partir de la précédente. Dans le cas de votre
pair_sequence
, c'est assez trivial. Ce n'est peut-être pas pour les générateurs complexes.Vous avez également besoin d'un moyen d'indiquer la résiliation. Si ce que vous retournez est "semblable à un pointeur", et que NULL ne doit pas être une valeur valide, vous pouvez utiliser un pointeur NULL comme indicateur de fin. Sinon, vous avez besoin d'un signal hors bande.
la source
Quelque chose comme ça est très similaire:
Utiliser l'opérateur () n'est qu'une question de savoir ce que vous voulez faire avec ce générateur, vous pouvez également le construire sous forme de flux et vous assurer qu'il s'adapte à un istream_iterator, par exemple.
la source
Utilisation de range-v3 :
la source
Quelque chose comme ça :
Exemple d'utilisation:
Imprime les nombres de 0 à 99
la source
Eh bien, aujourd'hui, je cherchais également une implémentation de collection facile sous C ++ 11. En fait, j'ai été déçu, car tout ce que j'ai trouvé est trop éloigné de choses comme les générateurs python, ou l'opérateur de rendement C # ... ou trop compliqué.
Le but est de faire une collection qui n'émettra ses éléments que lorsque cela sera nécessaire.
Je voulais que ce soit comme ça:
J'ai trouvé ce message, la meilleure réponse à mon humble avis était sur boost.coroutine2, par Yongwei Wu . Puisqu'il est le plus proche de ce que l'auteur voulait.
Il vaut la peine d'apprendre les couroutines boost .. Et je le ferai peut-être le week-end. Mais jusqu'à présent, j'utilise ma toute petite implémentation. J'espère que cela aide quelqu'un d'autre.
Voici un exemple d'utilisation, puis de mise en œuvre.
Exemple.cpp
Generator.h
la source
Cette réponse fonctionne en C (et je pense donc que cela fonctionne aussi en C ++)
C'est une manière simple et non orientée objet d'imiter un générateur. Cela a fonctionné comme prévu pour moi.
la source
Tout comme une fonction simule le concept de pile, les générateurs simulent le concept de file d'attente. Le reste est de la sémantique.
En remarque, vous pouvez toujours simuler une file d'attente avec une pile en utilisant une pile d'opérations au lieu de données. En pratique, cela signifie que vous pouvez implémenter un comportement de type file d'attente en renvoyant une paire, dont la deuxième valeur a la fonction suivante à appeler ou indique que nous n'avons plus de valeurs. Mais c'est plus général que ce que fait le rendement par rapport au rendement. Il permet de simuler une file d'attente de toutes les valeurs plutôt que des valeurs homogènes que vous attendez d'un générateur, mais sans garder une file d'attente interne complète.
Plus précisément, étant donné que C ++ n'a pas d'abstraction naturelle pour une file d'attente, vous devez utiliser des constructions qui implémentent une file d'attente en interne. La réponse qui a donné l'exemple avec les itérateurs est donc une implémentation décente du concept.
Cela signifie pratiquement que vous pouvez implémenter quelque chose avec une fonctionnalité de file d'attente simple si vous voulez juste quelque chose de rapide et ensuite consommer les valeurs de la file d'attente comme vous le feriez avec les valeurs générées par un générateur.
la source