Eu tenho alguns exemplos de código Python que preciso imitar em C ++. Não preciso de nenhuma solução específica (como soluções de rendimento baseadas em co-rotina, embora também sejam respostas aceitáveis), simplesmente preciso reproduzir a semântica de alguma maneira.
Pitão
Este é um gerador de sequência básico, claramente grande demais para armazenar uma versão materializada.
def pair_sequence():
for i in range(2**32):
for j in range(2**32):
yield (i, j)
O objetivo é manter duas instâncias da sequência acima e iterar sobre elas em semi-lockstep, mas em blocos. No exemplo abaixo, o first_pass
usa a sequência de pares para inicializar o buffer e second_pass
regenera a mesma sequência exata e processa o buffer novamente.
def run():
seq1 = pair_sequence()
seq2 = pair_sequence()
buffer = [0] * 1000
first_pass(seq1, buffer)
second_pass(seq2, buffer)
... repeat ...
C ++
A única coisa que posso encontrar para uma solução em C ++ é imitar yield
com co-rotinas C ++, mas não encontrei nenhuma boa referência sobre como fazer isso. Também estou interessado em soluções alternativas (não gerais) para este problema. Não tenho orçamento de memória suficiente para manter uma cópia da sequência entre as passagens.
Respostas:
Existem geradores em C ++, apenas com outro nome: Iteradores de entrada . Por exemplo, ler de
std::cin
é semelhante a ter um gerador dechar
.Você simplesmente precisa entender o que um gerador faz:
Em seu exemplo trivial, é bastante fácil. Conceitualmente:
Claro, nós envolvemos isso como uma classe adequada:
Então, hum sim ... pode ser que C ++ seja um pouco mais prolixo :)
fonte
Em C ++ existem iteradores, mas implementar um iterador não é simples: é preciso consultar os conceitos do iterador e projetar cuidadosamente a nova classe do iterador para implementá-los. Felizmente, Boost tem um modelo iterator_facade que deve ajudar a implementar os iteradores e geradores compatíveis com iteradores.
Às vezes, uma co-rotina sem pilha pode ser usada para implementar um iterador .
PS Veja também este artigo que menciona um
switch
hack de Christopher M. Kohlhoff e Boost.Coroutine de Oliver Kowalke. O trabalho de Oliver Kowalke é uma continuação do Boost.Coroutine de Giovanni P. Deretta.PS Acho que você também pode escrever uma espécie de gerador com lambdas :
Ou com um functor:
PS Aqui está um gerador implementado com as corrotinas Mordor :
fonte
Como Boost.Coroutine2 agora o suporta muito bem (eu o encontrei porque queria resolver exatamente o mesmo
yield
problema), estou postando o código C ++ que corresponde à sua intenção original:Neste exemplo,
pair_sequence
não aceita argumentos adicionais. Se for necessário,std::bind
ou um lambda deve ser usado para gerar um objeto de função que leva apenas um argumento (depush_type
), quando é passado para ocoro_t::pull_type
construtor.fonte
Todas as respostas que envolvem escrever seu próprio iterador estão completamente erradas. Essas respostas perdem totalmente o objetivo dos geradores Python (um dos maiores e únicos recursos da linguagem). O mais importante sobre geradores é que a execução continua de onde parou. Isso não acontece com iteradores. Em vez disso, você deve armazenar manualmente as informações de estado de forma que, quando o operador ++ ou o operador * for chamado novamente, as informações corretas estarão disponíveis no início da próxima chamada de função. É por isso que escrever seu próprio iterador C ++ é uma dor gigantesca; enquanto os geradores são elegantes e fáceis de ler e escrever.
Não acho que exista um bom análogo para geradores Python em C ++ nativo, pelo menos não ainda (há rumores de que o rendimento cairá em C ++ 17 ). Você pode obter algo semelhante recorrendo a terceiros (por exemplo, a sugestão de Boost de Yongwei) ou fazendo o seu próprio.
Eu diria que a coisa mais próxima em C ++ nativo são threads. Um thread pode manter um conjunto suspenso de variáveis locais e pode continuar a execução de onde parou, muito parecido com os geradores, mas você precisa lançar um pouco de infraestrutura adicional para suportar a comunicação entre o objeto gerador e seu chamador. Por exemplo
No entanto, esta solução tem várias desvantagens:
fonte
Você provavelmente deve verificar os geradores em std :: experimental no Visual Studio 2015, por exemplo: https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/
Acho que é exatamente o que você está procurando. Geradores gerais devem estar disponíveis em C ++ 17, pois este é apenas um recurso experimental do Microsoft VC.
fonte
Se você só precisa fazer isso para um número relativamente pequeno de geradores específicos, você pode implementar cada um como uma classe, onde os dados do membro são equivalentes às variáveis locais da função do gerador Python. Então você tem uma próxima função que retorna a próxima coisa que o gerador produziria, atualizando o estado interno enquanto o faz.
Isso é basicamente semelhante a como os geradores Python são implementados, eu acredito. A principal diferença é que eles podem se lembrar de um deslocamento no bytecode da função do gerador como parte do "estado interno", o que significa que os geradores podem ser escritos como loops contendo rendimentos. Em vez disso, você teria que calcular o próximo valor do anterior. No caso do seu
pair_sequence
, isso é bastante trivial. Pode não ser para geradores complexos.Você também precisa de alguma forma de indicar a rescisão. Se o que você está retornando é "semelhante a um ponteiro", e NULL não deve ser um valor rendível válido, você pode usar um ponteiro NULL como um indicador de finalização. Caso contrário, você precisa de um sinal fora de banda.
fonte
Algo assim é muito semelhante:
Usar o operator () é apenas uma questão de o que você deseja fazer com este gerador, você também pode construí-lo como um fluxo e certificar-se de que ele se adapta a um istream_iterator, por exemplo.
fonte
Usando range-v3 :
fonte
Algo como este :
Exemplo de uso:
Irá imprimir os números de 0 a 99
fonte
Bem, hoje eu também estava procurando uma implementação de coleção fácil em C ++ 11. Na verdade, fiquei desapontado, porque tudo o que encontrei está muito longe de coisas como geradores de python ou operador de rendimento C # ... ou muito complicado.
O objetivo é fazer coleta que irá emitir seus itens apenas quando for necessário.
Eu queria que fosse assim:
Achei este post, a melhor resposta IMHO foi sobre boost.coroutine2, de Yongwei Wu . Por ser o mais próximo do que o autor queria.
Vale a pena aprender as rotinas de reforço. E talvez o faça nos fins de semana. Mas até agora estou usando minha implementação muito pequena. Espero que ajude outra pessoa.
Abaixo está um exemplo de uso e implementação.
Example.cpp
Generator.h
fonte
Esta resposta funciona em C (e, portanto, acho que também funciona em C ++)
Esta é uma maneira simples e não orientada a objetos de imitar um gerador. Isso funcionou conforme o esperado para mim.
fonte
Assim como uma função simula o conceito de pilha, os geradores simulam o conceito de fila. O resto é semântica.
Como uma observação lateral, você sempre pode simular uma fila com uma pilha usando uma pilha de operações em vez de dados. O que isso significa na prática é que você pode implementar um comportamento semelhante ao de fila, retornando um par, cujo segundo valor tem a próxima função a ser chamada ou indica que estamos sem valores. Mas isso é mais geral do que o rendimento versus retorno. Ele permite simular uma fila de quaisquer valores ao invés de valores homogêneos que você espera de um gerador, mas sem manter uma fila interna cheia.
Mais especificamente, como o C ++ não tem uma abstração natural para uma fila, você precisa usar construções que implementam uma fila internamente. Portanto, a resposta que deu o exemplo com iteradores é uma implementação decente do conceito.
O que isso significa na prática é que você pode implementar algo com funcionalidade de fila básica se quiser apenas algo rápido e, em seguida, consumir os valores da fila da mesma forma que consumiria os valores produzidos por um gerador.
fonte