yujaiyu
yujaiyu

Reputation: 9003

How to make a hierarchy of different object "generators" in plain C++11

What I need is the following hierarchy of classes (given here as a sketch):

class DataClass {}

class AbstractGenerator {
public:
    // Generates DataClass objects one by one. In a lazy manner
    virtual DataClass produce() = 0;
}

class RandGenerator : AbstractGenerator {
public:
    RandGenerator(int maximal_int) : maximal(maximal_int) {}
    DataClass produce() {
        // get a random number from 0 to this->maximal
        // make a DataClass object from the random int and return it
    }

private:
    int maximal;
}

class FromFileGenerator : AbstractGenerator {
public:
    FromFileGenerator(string file_name) : f_name(file_name) {}
    DataClass produce() {
        // read the next line from the file
        // deserialize the DataClass object from the line and return it
    }

private:
    string f_name;
}

What I want to support for both RandGenerator and FromFileGnerator is:

RandGenerator rg();
for (DataClass data : rg) {...}

And also some method of taking "first n elements of the generator".

What are the appropriate tools in the plain C++11 that one could use to achieve this, or whatever is the closest to this in C++11?

Upvotes: 1

Views: 93

Answers (2)

Mooing Duck
Mooing Duck

Reputation: 66922

boost::function_input_iterator is the normal tool for this job, but since you want "plain" C++, we can just reimplement the same concept.

class generator_iterator {
   std::shared_ptr<AbstractGenerator> generator;
public:
   using iterator_category = std::input_iterator_tag;
   generator_iterator(std::shared_ptr<AbstractGenerator> generator_)
       :generator(generator_) {}
   
   DataClass operator*(){return generator->produce();}
   generator_iterator& operator++(){return *this;}
   generator_iterator operator++(int){return *this;}
   //plus all the other normal bits for an output_iterator
};

And then in your AbstractGenerator class, provide begin and end methods.

generator_iterator begin() {return {this};}
generator_iterator end() {return {nullptr};} //or other logic depending on how you want to detect the end of a series

Upvotes: 2

Daniel Jour
Daniel Jour

Reputation: 16156

Add a begin and end member function to AbstractGenerator, which return iterators which call the produce member function.

Such an iterator (demo) could look similar to this:

template<typename Fn>
struct CallRepeatedlyIterator
{
    using iterator_category = std::input_iterator_tag;
    using value_type = typename std::result_of<Fn()>::type;

    // Not sure if that's correct (probably not):
    using difference_type = unsigned;
    using pointer = value_type *;
    using reference = value_type &;
    
    
    bool is_end;
    union {
        Fn fn;
    };
    union {
        value_type buffer;
    };
    
    value_type operator*() const
    {
        return buffer;
    }
    
    CallRepeatedlyIterator & operator++()
    {
        buffer = fn();
        return *this;
    }
    
    CallRepeatedlyIterator()
    : is_end(true)
    {}
    
    explicit CallRepeatedlyIterator(Fn f)
    : is_end(false)
    {
        new (&fn) Fn(f);
        new (&buffer) value_type(fn());
    }
    
    bool operator==(CallRepeatedlyIterator const & other) const
    {
        return is_end && other.is_end;
    }
    
    bool operator!=(CallRepeatedlyIterator const & other) const
    {
        return !(*this == other);
    }

    // NOTE: A destructor is missing here! It needs to destruct fn and buffer 
    //       if its not the end iterator.
};

Now your begin member function returns such an iterator which calls produce (e.g. using a by reference capturing lambda) and end returns an "end" iterator.

This means that your for loop would run forever (no way to reach the end iterator)!

Upvotes: 1

Related Questions