Tengo un código de Python de ejemplo que necesito imitar en C ++. No necesito ninguna solución específica (como soluciones de rendimiento basadas en co-rutinas, aunque también serían respuestas aceptables), simplemente necesito reproducir la semántica de alguna manera.
Pitón
Este es un generador de secuencias básico, claramente demasiado grande para almacenar una versión materializada.
def pair_sequence():
for i in range(2**32):
for j in range(2**32):
yield (i, j)
El objetivo es mantener dos instancias de la secuencia anterior e iterar sobre ellas en semicerrado, pero en partes. En el siguiente ejemplo, el first_pass
usa la secuencia de pares para inicializar el búfer, y second_pass
regenera la misma secuencia exacta y procesa el búfer nuevamente.
def run():
seq1 = pair_sequence()
seq2 = pair_sequence()
buffer = [0] * 1000
first_pass(seq1, buffer)
second_pass(seq2, buffer)
... repeat ...
C ++
Lo único que puedo encontrar para una solución en C ++ es imitar yield
con corrutinas de C ++, pero no he encontrado ninguna buena referencia sobre cómo hacer esto. También estoy interesado en soluciones alternativas (no generales) para este problema. No tengo suficiente presupuesto de memoria para guardar una copia de la secuencia entre pasadas.
Respuestas:
Los generadores existen en C ++, con otro nombre: Iteradores de entrada . Por ejemplo, leer de
std::cin
es similar a tener un generador dechar
.Simplemente necesita comprender lo que hace un generador:
En su ejemplo trivial, es bastante fácil. Conceptualmente:
Por supuesto, envolvemos esto como una clase adecuada:
Así que hum, sí ... podría ser que C ++ es un poco más detallado :)
fuente
En C ++ hay iteradores, pero implementar un iterador no es sencillo: hay que consultar los conceptos del iterador y diseñar cuidadosamente la nueva clase de iterador para implementarlos. Afortunadamente, Boost tiene una plantilla iterator_facade que debería ayudar a implementar los iteradores y los generadores compatibles con iteradores.
A veces, se puede usar una corrutina sin pila para implementar un iterador .
PD: Consulte también este artículo que menciona un
switch
truco de Christopher M. Kohlhoff y Boost.Coroutine de Oliver Kowalke. El trabajo de Oliver Kowalke es una continuación de Boost.Coroutine de Giovanni P. Deretta.PD Creo que también puedes escribir una especie de generador con lambdas :
O con un functor:
PD Aquí hay un generador implementado con las corrutinas de Mordor :
fuente
Dado que Boost.Coroutine2 ahora lo admite muy bien (lo encontré porque quería resolver exactamente el mismo
yield
problema), estoy publicando el código C ++ que coincide con su intención original:En este ejemplo,
pair_sequence
no toma argumentos adicionales. Si es necesario,std::bind
o se debe usar una lambda para generar un objeto de función que toma solo un argumento (depush_type
), cuando se pasa alcoro_t::pull_type
constructor.fuente
Todas las respuestas que implican escribir su propio iterador son completamente incorrectas. Tales respuestas pierden por completo el sentido de los generadores de Python (una de las características más grandes y únicas del lenguaje). Lo más importante sobre los generadores es que la ejecución se reanuda donde se detuvo. Esto no les sucede a los iteradores. En su lugar, debe almacenar manualmente la información de estado de modo que cuando se llame de nuevo al operador ++ o al operador *, la información correcta esté en su lugar al comienzo de la siguiente llamada a la función. Es por eso que escribir su propio iterador de C ++ es un dolor gigantesco; mientras que los generadores son elegantes y fáciles de leer + escribir.
No creo que haya un buen análogo para los generadores de Python en C ++ nativo, al menos no todavía (hay un rummor de que el rendimiento aterrizará en C ++ 17 ). Puede obtener algo similar recurriendo a un tercero (por ejemplo, la sugerencia de Boost de Yongwei) o lanzando el suyo.
Yo diría que lo más parecido en C ++ nativo son los hilos. Un subproceso puede mantener un conjunto suspendido de variables locales y puede continuar la ejecución donde se detuvo, de manera muy similar a los generadores, pero necesita un poco de infraestructura adicional para admitir la comunicación entre el objeto generador y su llamador. P.ej
Sin embargo, esta solución tiene varias desventajas:
fuente
Probablemente debería verificar los generadores en std :: experimental en Visual Studio 2015, por ejemplo: https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/
Creo que es exactamente lo que estás buscando. Los generadores generales deberían estar disponibles en C ++ 17 ya que esta es solo una característica experimental de Microsoft VC.
fuente
Si solo necesita hacer esto para un número relativamente pequeño de generadores específicos, puede implementar cada uno como una clase, donde los datos de los miembros son equivalentes a las variables locales de la función del generador de Python. Luego, tiene una función siguiente que devuelve lo siguiente que produciría el generador, actualizando el estado interno a medida que lo hace.
Esto es básicamente similar a cómo se implementan los generadores de Python, creo. La principal diferencia es que pueden recordar un desplazamiento en el código de bytes para la función del generador como parte del "estado interno", lo que significa que los generadores se pueden escribir como bucles que contienen rendimientos. En su lugar, tendría que calcular el siguiente valor del anterior. En el caso de usted
pair_sequence
, eso es bastante trivial. Puede que no sea para generadores complejos.También necesita alguna forma de indicar la terminación. Si lo que está devolviendo es "similar a un puntero", y NULL no debería ser un valor válido válido, puede usar un puntero NULL como indicador de terminación. De lo contrario, necesita una señal fuera de banda.
fuente
Algo como esto es muy similar:
Usar el operador () es solo una cuestión de lo que quiere hacer con este generador, también puede construirlo como un flujo y asegurarse de que se adapte a un istream_iterator, por ejemplo.
fuente
Usando range-v3 :
fuente
Algo como esto :
Ejemplo de uso:
Imprimirá los números del 0 al 99
fuente
Bueno, hoy también estaba buscando una implementación de colección fácil en C ++ 11. En realidad, me decepcionó, porque todo lo que encontré está demasiado lejos de cosas como los generadores de python o el operador de rendimiento de C # ... o demasiado complicado.
El propósito es realizar una colección que emitirá sus ítems solo cuando sea requerido.
Quería que fuera así:
Encontré esta publicación, en mi humilde opinión, la mejor respuesta fue sobre boost.coroutine2, de Yongwei Wu . Ya que es lo más cercano a lo que quería el autor.
Vale la pena aprender couroutines de impulso .. Y quizás lo haga los fines de semana. Pero hasta ahora estoy usando mi implementación muy pequeña. Espero que ayude a alguien más.
A continuación se muestra un ejemplo de uso y luego implementación.
Example.cpp
Generator.h
fuente
Esta respuesta funciona en C (y, por lo tanto, creo que también funciona en c ++)
Esta es una forma sencilla, no orientada a objetos, de imitar un generador. Esto funcionó como se esperaba para mí.
fuente
Así como una función simula el concepto de pila, los generadores simulan el concepto de cola. El resto es semántica.
Como nota al margen, siempre puede simular una cola con una pila utilizando una pila de operaciones en lugar de datos. Lo que eso significa en la práctica es que puede implementar un comportamiento similar a una cola devolviendo un par, cuyo segundo valor tiene la siguiente función a llamar o indica que no tenemos valores. Pero esto es más general que lo que hace el rendimiento frente al rendimiento. Permite simular una cola de cualquier valor en lugar de valores homogéneos que espera de un generador, pero sin mantener una cola interna completa.
Más específicamente, dado que C ++ no tiene una abstracción natural para una cola, debe utilizar construcciones que implementen una cola internamente. Entonces, la respuesta que dio el ejemplo con iteradores es una implementación decente del concepto.
Lo que esto significa en la práctica es que puede implementar algo con la funcionalidad de cola básica si solo desea algo rápido y luego consumir los valores de la cola de la misma manera que consumiría los valores generados por un generador.
fuente