1

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?

foki
  • 8,624
  • 6
  • 33
  • 32
  • 2
    To enable range for loops you need to provide iterator for the given class. There are plenty of resources online how to do it. You can also refer to the following question https://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for-loops – michelson Jul 29 '20 at 17:06
  • @JeJo No, that should be a single object that generates other objects. That's how Python generators work, for example. – foki Jul 29 '20 at 17:44
  • @michelson The issue there is that I need to support an abstract class and each of its children would define a specific advancing mechanism. A generic `operator++` would not work. For example, one would read from a PRNG, another would deserialize objects from a file. – foki Jul 29 '20 at 17:51
  • @foki and have you thought about streams to do the job you want? Couse, in my opinion, you can either introduce some collection to keep the data and just iterating over it or, if the size is unknown, use streams. This range for loop thing is this something required or you can just skip it, providing an alternative - stream? – michelson Jul 29 '20 at 22:06
  • @michelson What is the difference between iterators and streams in this case? – foki Jul 29 '20 at 22:10
  • This looks like you need a [`boost::function_output_iterator`](https://www.boost.org/doc/libs/1_66_0/libs/iterator/doc/function_output_iterator.html) – Mooing Duck Jul 30 '20 at 01:52
  • 1
    @michelson: Why in the world would someone use streams in C++ for anything like this? – Mooing Duck Jul 30 '20 at 02:00
  • You are going to want `AbstractGenerator` to have a way of signalling that it has no more elements. C++ doesn't catch `stop_iteration` to end `for(auto val : range)` like Python does – Caleth Jul 30 '20 at 08:24

2 Answers2

2

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
Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • 2
    Why `output_iterator_tag` ... repeatedly calling a function with side effects and using its return value is more like an input iterator, no? – Daniel Jour Jul 30 '20 at 07:23
  • your `++`s don't work. `generator_iterator& operator++(){return *this;} generator_iterator operator++(int){return *this;}` – Caleth Jul 30 '20 at 08:19
  • 1
    `function_output_iterator` consumes values. I think you mean [`function_input_iterator`](https://www.boost.org/doc/libs/1_73_0/libs/iterator/doc/function_input_iterator.html) – Caleth Jul 30 '20 at 08:29
  • 1
    @Caleth: Despite writing thousands of iterators, I always get input/output backwards. The fact that Input produces values, and Output consumes values always baffles me. Thanks, Fixed. – Mooing Duck Jul 30 '20 at 16:20
1

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)!

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63