Generating functors with iterator behavior

戏子无情 提交于 2021-02-17 15:47:07

问题


I have a question, which very likely has been asked like this before, because I think what I want is something that a considerable amount of people would want. However I could not come up with any way of expressing it that would return what I wanted in search (not google, not here). So maybe the answer here is just a single term used to describe what I mean.

What I want to implement is something that roughly does the following:

  1. It can take a functor struct/class and generate a sequence of values for said functor based on the functor's function. It should be possible to use a stateful functor, i.e. it should be possible to instantiate a functor in state a and let it run until it is in state b, generating the range of values {f(a), f(a+1), ..., f(b)}, where f(a+1) represents the next item in the series represented by f.

  2. It behaves like an iterator, i.e. it can be passed instead of an iterator e.g. to fill a vector with values.

I think the name should be generator or generating iterator, because that's what it does, but I have been very unsuccessful in finding something with that term. I have written my own implementation, but it has its issues, and I wanted to ask, if there is such a thing before putting any more effort into it.

To save you the work of pasting all the subsequent code in case you want to try this out I put it on ideone. I think after running the code it is pretty clear what it does.

My current implementation looks like this (fyi this is a shortened version, where some stuff like -- and - are missing, so yes I do implement them, and thus it can work at least as bidirectional iterator. I also have a [] function, so I thought about making it random_access.):

template <class F>
class generator{
public:
//typedefs required for iterator-use
typedef typename F::value_type value_type;
typedef typename F::step_type step_type;
typedef value_type * pointer;
typedef value_type & reference;
typedef typename F::size_type size_type;
typedef typename F::difference_type difference_type;
typedef std::bidirectional_iterator_tag iterator_category;

generator(value_type init, step_type step) : t(init), step(step){}

generator<F> &operator++() {
    t += step; return *this;
}

generator<F> &
operator+=(size_type n)
{
    t += n * step;
    return *this;
}

generator<F>
operator+(size_type n)
{
    return generator(*this) += n;
}

value_type operator*() const {
    return f(t);
}

value_type operator*() const {
    return f(t);
}

friend bool operator==(const generator<F> &lhs, const generator<F> &rhs){
    return lhs.t == rhs.t;
}
friend bool operator!=(const generator<F> &lhs, const generator<F> &rhs){
    return !(lhs == rhs);
}
private:
    value_type t;
    value_type step;
    F f;
};

I use a different template to provide the typedefs as easily as possible:

template <typename T>
struct simple_generator_function
{
    typedef T value_type;
    typedef T step_type;
    typedef T difference_type;
    typedef size_t size_type;
};

Right now these two work together with a concrete "generator" like this:

template <typename T>
struct square_generator : public simple_generator_function<T> {

    T operator()(T t) const{
        return t * t;
    }
};

int main(void) {
    using int_sqg = generator<square_generator<int>>;
    //starting at initial value 1 take steps of size 4
    int_sqg gen(1, 1);
    //fill with all before inital + n * step
    vector<int> v(gen, gen+10);
    copy(begin(v), end(v), ostream_iterator<int>(cout, ","));
    cout << '\n';
}

Long story short: Is there a boost or other library, which offers this in a somewhat reliable manner and what is the name of such a iterator/functor-mix in general?

EDIT 1:

I think any solution can at best be an InputIterator, because as far as I have researched it, all other iterators would have to return a reference from operator*(), which is out of the question in this case. Very likely, it comes down to writing a template that converts a regular functor into an InputIterator.

Status: the answers so far are good, but I have put some thought into this for quite a while before asking, and I had thought about similar solutions, so my question is not really answered. I have updated requirement 1.) to - hopefully - reflect more clearly what I want. If nothing comes of this I will probably try to refine my current code into a more stable version and put it on github.

EDIT 2 (End of Bounty):

Even though I am not entirely satisfied with the solution, boost::irange in combination with boost::transformed as ectamur suggested come closest to doing what I want, so I will give the bounty to him.


回答1:


The Boost.Range way to solve this problem is to use the transform iterator adaptor:

auto rng = boost::irange(1, 10)
    | boost::adaptors::transformed([](int i) { return i * i; });
std::vector<int> v{rng.begin(), rng.end()};

Note how this separates the concerns of the transformation from the start/stop/step (optional) parameters of the input range.




回答2:


Sure, we can just write our own iterator:

template <typename F, typename Value>
class func_iterator
: std::iterator<
    std::random_access_iterator_tag,
    typename std::result_of<F(Value)>::type,
    Value,
    typename std::result_of<F(Value)>::type,
    typename std::result_of<F(Value)>::type>
{ .. };

This iterator needs three things: a function (F f), the current value and the step (Value value, step). Dereferencing will calculate the value of the function each time:

using T = typename std::result_of<F(Value)>::type;
T operator*() { return f(value); }

Selected iterating functions (omitting postfix since they look the same):

func_iterator& operator++() {
    value += step;
    return *this;
}

func_iterator& operator--() {
    value -= step;
    return *this;
}

func_iterator operator+(Value amt) {
    return func_iterator{f, value + amt * step, step};
}

Difference between iterators (for std::distance) and equality:

Value operator-(const func_iterator& rhs) {
    assert(step == rhs.step);
    return (value - rhs.value) / step;
}

bool operator==(const func_iterator& rhs) {
    return value == rhs.value && step == rhs.step;
}

And finally a function to make an iterator for us:

template <typename F, typename Value>
func_iterator<F, Value> make_func_iterator(F f, Value v, Value s = 1) {
    return func_iterator<F, Value>{f, v, s};
}

Putting that together, I can do something like:

auto sq_it = make_func_iterator([](int x){return x*x;}, 1);
std::vector<int> squares{sq_it, sq_it + 10}; // v now holds {1, 4, 9, 16, ..., 100}

Or just:

// get a bunch of even numbers, complicatedly:
auto ident = make_func_iterator([](int x){return x;}, 2, 2);
std::vector<int> evens{ident, ident+200}; // holds {2, 4, ..., 400}



回答3:


I wonder if this could be called tabulation. If so, what do you thing about the following interface?

#include <iostream>
#include <vector>
#include <list>

#include "tabulate.hpp"

template<typename T>
void show(const T& data) {
  for(const auto & x: data) std::cout << x << " ";
  std::cout << std::endl;
}

int main() {
  auto fun = [](double x) { return 2.0 * x; };

  std::vector<double> x  {1, 2, 3, 4, 5};

  std::cout << "using range-for" << std::endl;
  for(const auto & fx : tabulate(fun, x.begin(), x.end())) {
    std::cout << fx << std::endl;
  }

  std::cout << "initializing a vector" << std::endl;
  auto init = tabulate(fun, x.begin(), x.end());
  std::vector<double> values(init.begin(), init.end());
  show(values);

  std::cout << "automatic construction of vector" << std::endl;
  auto in_vector = make_tabulation<std::vector<double>>(fun, x);
  show(in_vector);

  std::cout << "automatic construction of list" << std::endl;
  auto in_list   = make_tabulation<std::list<double>>(fun, x);
  show(in_list);
}

Which is implemented by the following header:

#pragma once
#include <iterator>

template<typename Fun,
         typename InputIt,
         typename T = typename std::iterator_traits<InputIt>::value_type
         >
class tabulate_iterator

    : public std::iterator<std::input_iterator_tag, T> {

 public:

  tabulate_iterator()
      : m_is_valid(false) { }

  tabulate_iterator(Fun& fun, InputIt beg, InputIt end)
      : m_fun(&fun),
        m_beg(beg),
        m_end(end),
        m_is_valid(beg != end) {
    this->read();
  }

  const T& operator*() const {
    return m_current;
  }

  const T* operator->() const {
    return &(operator*());
  }

  tabulate_iterator& operator++() {
    this->read();
    return *this;
  }

  tabulate_iterator operator++(int) {
    auto tmp = *this;
    this->read();
    return tmp;
  }

  bool equals(const tabulate_iterator& other) const {
    return ((m_is_valid == other.m_is_valid) and
            (not m_is_valid));
  }

  bool operator==(const tabulate_iterator& other) const {
    return this->equals(other);
  }

  bool operator!=(const tabulate_iterator& other) const {
    return not this->equals(other);
  }

 private:

  void read() {
    if(m_is_valid and m_beg != m_end) {
      m_current = (*m_fun)(*m_beg++);
    } else {
      m_is_valid = false;
    }
  }

  T       m_current;
  Fun*    m_fun;
  InputIt m_beg;
  InputIt m_end;
  bool    m_is_valid;

};

template<typename Fun,
         typename InputIt,
         typename T = typename std::iterator_traits<InputIt>::value_type
         >
class tabulate_range {

 public:

  tabulate_iterator<Fun, InputIt, T> begin() const {
    return m_it;
  }

  tabulate_iterator<Fun, InputIt, T> end() const {
    return m_it_end;
  }

 private:

  template<typename Fun_, typename InputIt_, typename T_>
  friend tabulate_range<Fun_, InputIt_, T_> tabulate(Fun_, InputIt_, InputIt_);

  tabulate_range(Fun fun, InputIt beg, InputIt end)
      : m_it(fun, beg, end),
        m_it_end() { }

  tabulate_iterator<Fun, InputIt, T> m_it;
  tabulate_iterator<Fun, InputIt, T> m_it_end;
};

template<typename Fun,
         typename InputIt,
         typename T = typename std::iterator_traits<InputIt>::value_type
         >
tabulate_range<Fun, InputIt, T> tabulate(Fun fun, InputIt beg, InputIt end) {
  return tabulate_range<Fun, InputIt, T>(fun, beg, end);
}

template<typename OutContainer, typename Fun, typename InContainer>
OutContainer make_tabulation(Fun fun, const InContainer& x) {
  auto init = tabulate(fun, x.begin(), x.end());
  return OutContainer(init.begin(), init.end());
}

Some caveats: I just cracked this code in the spur of the moment, so bugs are likely; take this as a proof of concept.

Compilation (GCC 4.8.2/Linux; ICC 14.0.2 20140120/Linux):

{CXX} tabulate.cpp -std=c++11 -Wall -Wextra -Werror

Output:

$ ./a.out
using range-for
2
4
6
8
10
initializing a vector
2 4 6 8 10
automatic construction of vector
2 4 6 8 10
automatic construction of list
2 4 6 8 10



回答4:


The C++ library already offers some algorithms that implement most of the functionality you're trying to do on your own. I think it's better for you to adapt your template so that it would work seamlessly with the C++ library, instead.

I'm thinking specifically of std::generate().

So, you take what you're planning to do in your step #1, but replace step #2 with implementing an operator() that returns the next value in the sequence.

Then, you can let std::generate() take care of populating an actual sequence with your values.



来源:https://stackoverflow.com/questions/28263134/generating-functors-with-iterator-behavior

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!