Equivalent C++ to Python generator pattern

前端 未结 12 2351
Happy的楠姐
Happy的楠姐 2020-11-28 18:42

I\'ve got some example Python code that I need to mimic in C++. I do not require any specific solution (such as co-routine based yield solutions, although they would be acce

相关标签:
12条回答
  • 2020-11-28 19:12

    In C++ there are iterators, but implementing an iterator isn't straightforward: one has to consult the iterator concepts and carefully design the new iterator class to implement them. Thankfully, Boost has an iterator_facade template which should help implementing the iterators and iterator-compatible generators.

    Sometimes a stackless coroutine can be used to implement an iterator.

    P.S. See also this article which mentions both a switch hack by Christopher M. Kohlhoff and Boost.Coroutine by Oliver Kowalke. Oliver Kowalke's work is a followup on Boost.Coroutine by Giovanni P. Deretta.

    P.S. I think you can also write a kind of generator with lambdas:

    std::function<int()> generator = []{
      int i = 0;
      return [=]() mutable {
        return i < 10 ? i++ : -1;
      };
    }();
    int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;
    

    Or with a functor:

    struct generator_t {
      int i = 0;
      int operator() () {
        return i < 10 ? i++ : -1;
      }
    } generator;
    int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;
    

    P.S. Here's a generator implemented with the Mordor coroutines:

    #include <iostream>
    using std::cout; using std::endl;
    #include <mordor/coroutine.h>
    using Mordor::Coroutine; using Mordor::Fiber;
    
    void testMordor() {
      Coroutine<int> coro ([](Coroutine<int>& self) {
        int i = 0; while (i < 9) self.yield (i++);
      });
      for (int i = coro.call(); coro.state() != Fiber::TERM; i = coro.call()) cout << i << endl;
    }
    
    0 讨论(0)
  • 2020-11-28 19:13

    You should probably check generators in std::experimental in Visual Studio 2015 e.g: https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/

    I think it's exactly what you are looking for. Overall generators should be available in C++17 as this is only experimental Microsoft VC feature.

    0 讨论(0)
  • 2020-11-28 19:13

    Just as a function simulates the concept of a stack, generators simulate the concept of a queue. The rest is semantics.

    As a side note, you can always simulate a queue with a stack by using a stack of operations instead of data. What that practically means is that you can implement a queue-like behavior by returning a pair, the second value of which either has the next function to be called or indicates that we are out of values. But this is more general than what yield vs return does. It allows to simulate a queue of any values rather than homogeneous values that you expect from a generator, but without keeping a full internal queue.

    More specifically, since C++ does not have a natural abstraction for a queue, you need to use constructs which implement a queue internally. So the answer which gave the example with iterators is a decent implementation of the concept.

    What this practically means is that you can implement something with bare-bones queue functionality if you just want something quick and then consume queue's values just as you would consume values yielded from a generator.

    0 讨论(0)
  • 2020-11-28 19:14

    Something like this:

    Example use:

    using ull = unsigned long long;
    
    auto main() -> int {
        for (ull val : range_t<ull>(100)) {
            std::cout << val << std::endl;
        }
    
        return 0;
    }
    

    Will print the numbers from 0 to 99

    0 讨论(0)
  • 2020-11-28 19:17

    This answer works in C (and hence i think works in c++ too)

    #include <stdio.h>
    
    const uint64_t MAX = 1ll<<32;
    
    typedef struct {
        uint64_t i, j;
    } Pair;
    
    Pair* generate_pairs()
    {
        static uint64_t i = 0;
        static uint64_t j = 0;
        
        Pair p = {i,j};
        if(j++ < MAX)
        {
            return &p;
        }
            else if(++i < MAX)
        {
            p.i++;
            p.j = 0;
            j = 0;
            return &p;
        }
        else
        {
            return NULL;
        }
    }
    
    int main()
    {
        while(1)
        {
            Pair *p = generate_pairs();
            if(p != NULL)
            {
                //printf("%d,%d\n",p->i,p->j);
            }
            else
            {
                //printf("end");
                break;
            }
        }
        return 0;
    }
    

    This is simple, non object-oriented way to mimic a generator. This worked as expected for me.

    0 讨论(0)
  • 2020-11-28 19:18

    Well, today I also was looking for easy collection implementation under C++11. Actually I was disappointed, because everything I found is too far from things like python generators, or C# yield operator... or too complicated.

    The purpose is to make collection which will emit its items only when it is required.

    I wanted it to be like this:

    auto emitter = on_range<int>(a, b).yield(
        [](int i) {
             /* do something with i */
             return i * 2;
        });
    

    I found this post, IMHO best answer was about boost.coroutine2, by Yongwei Wu. Since it is the nearest to what author wanted.

    It is worth learning boost couroutines.. And I'll perhaps do on weekends. But so far I'm using my very small implementation. Hope it helps to someone else.

    Below is example of use, and then implementation.

    Example.cpp

    #include <iostream>
    #include "Generator.h"
    int main() {
        typedef std::pair<int, int> res_t;
    
        auto emitter = Generator<res_t, int>::on_range(0, 3)
            .yield([](int i) {
                return std::make_pair(i, i * i);
            });
    
        for (auto kv : emitter) {
            std::cout << kv.first << "^2 = " << kv.second << std::endl;
        }
    
        return 0;
    }
    

    Generator.h

    template<typename ResTy, typename IndexTy>
    struct yield_function{
        typedef std::function<ResTy(IndexTy)> type;
    };
    
    template<typename ResTy, typename IndexTy>
    class YieldConstIterator {
    public:
        typedef IndexTy index_t;
        typedef ResTy res_t;
        typedef typename yield_function<res_t, index_t>::type yield_function_t;
    
        typedef YieldConstIterator<ResTy, IndexTy> mytype_t;
        typedef ResTy value_type;
    
        YieldConstIterator(index_t index, yield_function_t yieldFunction) :
                mIndex(index),
                mYieldFunction(yieldFunction) {}
    
        mytype_t &operator++() {
            ++mIndex;
            return *this;
        }
    
        const value_type operator*() const {
            return mYieldFunction(mIndex);
        }
    
        bool operator!=(const mytype_t &r) const {
            return mIndex != r.mIndex;
        }
    
    protected:
    
        index_t mIndex;
        yield_function_t mYieldFunction;
    };
    
    template<typename ResTy, typename IndexTy>
    class YieldIterator : public YieldConstIterator<ResTy, IndexTy> {
    public:
    
        typedef YieldConstIterator<ResTy, IndexTy> parent_t;
    
        typedef IndexTy index_t;
        typedef ResTy res_t;
        typedef typename yield_function<res_t, index_t>::type yield_function_t;
        typedef ResTy value_type;
    
        YieldIterator(index_t index, yield_function_t yieldFunction) :
                parent_t(index, yieldFunction) {}
    
        value_type operator*() {
            return parent_t::mYieldFunction(parent_t::mIndex);
        }
    };
    
    template<typename IndexTy>
    struct Range {
    public:
        typedef IndexTy index_t;
        typedef Range<IndexTy> mytype_t;
    
        index_t begin;
        index_t end;
    };
    
    template<typename ResTy, typename IndexTy>
    class GeneratorCollection {
    public:
    
        typedef Range<IndexTy> range_t;
    
        typedef IndexTy index_t;
        typedef ResTy res_t;
        typedef typename yield_function<res_t, index_t>::type yield_function_t;
        typedef YieldIterator<ResTy, IndexTy> iterator;
        typedef YieldConstIterator<ResTy, IndexTy> const_iterator;
    
        GeneratorCollection(range_t range, const yield_function_t &yieldF) :
                mRange(range),
                mYieldFunction(yieldF) {}
    
        iterator begin() {
            return iterator(mRange.begin, mYieldFunction);
        }
    
        iterator end() {
            return iterator(mRange.end, mYieldFunction);
        }
    
        const_iterator begin() const {
            return const_iterator(mRange.begin, mYieldFunction);
        }
    
        const_iterator end() const {
            return const_iterator(mRange.end, mYieldFunction);
        }
    
    private:
        range_t mRange;
        yield_function_t mYieldFunction;
    };
    
    template<typename ResTy, typename IndexTy>
    class Generator {
    public:
        typedef IndexTy index_t;
        typedef ResTy res_t;
        typedef typename yield_function<res_t, index_t>::type yield_function_t;
    
        typedef Generator<ResTy, IndexTy> mytype_t;
        typedef Range<IndexTy> parent_t;
        typedef GeneratorCollection<ResTy, IndexTy> finalized_emitter_t;
        typedef  Range<IndexTy> range_t;
    
    protected:
        Generator(range_t range) : mRange(range) {}
    public:
        static mytype_t on_range(index_t begin, index_t end) {
            return mytype_t({ begin, end });
        }
    
        finalized_emitter_t yield(yield_function_t f) {
            return finalized_emitter_t(mRange, f);
        }
    protected:
    
        range_t mRange;
    };      
    
    0 讨论(0)
提交回复
热议问题